﻿﻿/* **********************************************************************************
 * Copyright (c) 2011 John Hughes
 *
 * j9Script is licenced under the Microsoft Reciprocal License (Ms-RL).
 *
 * Project Website: http://j9Script.codeplex.com/
 * **********************************************************************************/

using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.Specialized;
using System.Reflection;
using System.IO;
using System.Data;
using System.Collections;
using System.Linq;
using System.Data.OleDb;
using System.Runtime.Serialization;
using System.Data.Odbc;
using System.Diagnostics;

namespace J9Script
{
    #region Attributes
    [Serializable]
    public abstract class ScriptObjectMemberBaseAttribute : System.Attribute
    {
        private string _displayName;
        private string _description;
        private bool _hide;

        public ScriptObjectMemberBaseAttribute(string classDisplayName, string description)
            : this(classDisplayName, description, false)
        {
        }
        public ScriptObjectMemberBaseAttribute(string classDisplayName, string description, bool hide)
        {
            _displayName = classDisplayName;
            _description = description;
            _hide = hide;
        }

        public string DisplayName
        {
            get
            {
                return _displayName;
            }
        }

        public string Description
        {
            get
            {
                return _description;
            }
        }

        public bool Hide
        {
            get
            {
                return _hide;
            }
        }
    }





    [Serializable]
    public abstract class ScriptObjectParameterBaseAttribute : System.Attribute
    {
        private string _description;
        private string _mask;  // For format see: http://msdn.microsoft.com/en-us/library/system.windows.forms.maskedtextbox.mask.aspx
        private double _minimumNumericValue;
        private double _maximumNumericValue;
        private string[] _stringList;
        private double[] _numberListValues;
        private string[] _numberListNames;
        private int _displayOrder;

        public ScriptObjectParameterBaseAttribute(string description) : this (description, 0)
        {
        }

        public ScriptObjectParameterBaseAttribute(string description, int displayOrder)
        {
            _description = description;
            _mask = null;
            _minimumNumericValue = 0;
            _maximumNumericValue = 0;
            _stringList = null;
            _numberListValues = null;
            _numberListNames = null;
            _displayOrder = displayOrder;
        }

        public ScriptObjectParameterBaseAttribute(string description, string mask) : this(description, mask, 0)
        {
        }

        public ScriptObjectParameterBaseAttribute(string description, string mask, int displayOrder)
        {
            _description = description;
            _mask = mask;
            _minimumNumericValue = 0;
            _maximumNumericValue = 0;
            _stringList = null;
            _numberListValues = null;
            _numberListNames = null;
            _displayOrder = displayOrder;
        }

        public ScriptObjectParameterBaseAttribute(string description, double minimumNumericValue, double maximumNumericValue) : this(description, minimumNumericValue, maximumNumericValue, 0)
        {
        }

        public ScriptObjectParameterBaseAttribute(string description, double minimumNumericValue, double maximumNumericValue, int displayOrder)
        {
            _description = description;
            _mask = null;
            _minimumNumericValue = minimumNumericValue;
            _maximumNumericValue = maximumNumericValue;
            _stringList = null;
            _numberListValues = null;
            _numberListNames = null;
            _displayOrder = displayOrder;
        }

        public ScriptObjectParameterBaseAttribute(string description, string[] valueList) : this(description, valueList, 0)
        {
        }

        public ScriptObjectParameterBaseAttribute(string description, string[] valueList, int displayOrder)
        {
            _description = description;
            _mask = null;
            _minimumNumericValue = 0;
            _maximumNumericValue = 0;
            _maximumNumericValue = 0;
            _stringList = valueList;
            _numberListValues = null;
            _numberListNames = null;
            _displayOrder = displayOrder;
        }

        public ScriptObjectParameterBaseAttribute(string description, double[] numberListValues, string[] numberListNames) : this(description, numberListValues, numberListNames, 0)
        {
        }

        public ScriptObjectParameterBaseAttribute(string description, double[] numberListValues, string[] numberListNames, int displayOrder)
        {
            if (numberListValues.Length != numberListNames.Length)
                throw new ArgumentException("The numberListValues and numberListNames lengths must be equal.");

            _description = description;
            _mask = null;
            _minimumNumericValue = 0;
            _maximumNumericValue = 0;
            _stringList = null;
            _numberListValues = numberListValues;
            _numberListNames = numberListNames;
            _displayOrder = displayOrder;
        }

        public string Description
        {
            get
            {
                return _description;
            }
        }

        public int DisplayOrder
        {
            get
            {
                return _displayOrder;
            }
        }

        public string[] GetStringList()
        {
            return _stringList;
        }


        public double[] GetNumericValues()
        {
            return (double[])_numberListValues.Clone();
        }

        public string[] GetNumericValueDisplayNames()
        {
            return (string[])_numberListNames.Clone();
        }

        public string[] GetNumericValueDisplayTexts()
        {
            string[] s = new string[_numberListValues.Length];
            for (int i=0; i<_numberListValues.Length; i++)
                s[i] = _numberListNames[i] + " (" + _numberListValues[i] + ")";

            return s;
        }

        public int GetNumericValueIndex(double value)
        {
            return Array.IndexOf(_numberListValues, value);
        }


        public int GetNumericValueIndexByDisplayText(string displayText, int defaultIfNoMatch)
        {
            string[] s = GetNumericValueDisplayTexts();

            for (int i = 0; i < s.Length; i++)
                if (s[i] == displayText)
                    return i;

            return defaultIfNoMatch;
        }

        public string GetNumericValueDisplayText(double value)
        {
            int valueIndex = this.GetNumericValueIndex(value);
            if (valueIndex > -1)
                return _numberListNames[valueIndex] + " (" + value + ")";
            else
                return "Invalid Value (" + value + ")";
        }

        public string GetDisplayText(string strValue)
        {
            if (this.HasNumberList)
            {
                double parameterValueDouble;
                if (double.TryParse(strValue, out parameterValueDouble))
                    return GetNumericValueDisplayText(parameterValueDouble);
                else
                    return "Invalid Value (" + strValue + ")";
            }

            return strValue;
        }

        /// <summary>
        /// For format see: http://msdn.microsoft.com/en-us/library/system.windows.forms.maskedtextbox.mask.aspx
        /// </summary>
        public string Mask
        {
            get
            {
                return _mask;
            }
        }

        public double MinimumNumericValue
        {
            get
            {
                return _minimumNumericValue;
            }
        }

        public double MaximumNumericValue
        {
            get
            {
                return _maximumNumericValue;
            }
        }

        public bool HasMask
        {
            get
            {
                return _mask != null;
            }
        }

        public bool HasNumericRange
        {
            get
            {
                return _minimumNumericValue != _maximumNumericValue;
            }
        }

        public bool HasStringList
        {
            get
            {
                return _stringList != null;
            }
        }

        public bool HasNumberList
        {
            get
            {
                return _numberListValues != null && _numberListValues.Length > 0;
            }
        }
    }



    [Serializable]
    public abstract class ScriptObjectNamedParameterBaseAttribute : ScriptObjectParameterBaseAttribute
    {
        private string _parameterName;

        protected string _displayTextArrayPropertyName;

        public ScriptObjectNamedParameterBaseAttribute(string parameterName, string description) : this(parameterName, description, 0)
        {
        }
        public ScriptObjectNamedParameterBaseAttribute(string parameterName, string description, int displayOrder)
            : base(description, displayOrder)
        {
            _parameterName = parameterName;
        }

        public ScriptObjectNamedParameterBaseAttribute(string parameterName, string description, string mask) : this (parameterName, description, mask, 0)
        {
        }
        public ScriptObjectNamedParameterBaseAttribute(string parameterName, string description, string mask, int displayOrder)
            : base(description, mask, displayOrder)
        {
            _parameterName = parameterName;
        }

        public ScriptObjectNamedParameterBaseAttribute(string parameterName, string description, double minimumNumericValue, double maximumNumericValue) : this(parameterName, description, minimumNumericValue, maximumNumericValue, 0)
        {
        }
        public ScriptObjectNamedParameterBaseAttribute(string parameterName, string description, double minimumNumericValue, double maximumNumericValue, int displayOrder)
            : base(description, minimumNumericValue, maximumNumericValue, displayOrder)
        {
            _parameterName = parameterName;
        }
        public ScriptObjectNamedParameterBaseAttribute(string parameterName, string description, double minimumNumericValue, double maximumNumericValue, string displayTextArrayPropertyName) : this(parameterName, description, minimumNumericValue, maximumNumericValue, displayTextArrayPropertyName, 0)
        {
        }
        public ScriptObjectNamedParameterBaseAttribute(string parameterName, string description, double minimumNumericValue, double maximumNumericValue, string displayTextArrayPropertyName, int displayOrder)
            : base(description, minimumNumericValue, maximumNumericValue, displayOrder)
        {
            _parameterName = parameterName;
            _displayTextArrayPropertyName = displayTextArrayPropertyName;
        }

        public ScriptObjectNamedParameterBaseAttribute(string parameterName, string description, string[] valueList) : this (parameterName, description, valueList, 0)
        {
        }
        public ScriptObjectNamedParameterBaseAttribute(string parameterName, string description, string[] valueList, int displayOrder)
            : base(description, valueList, displayOrder)
        {
            _parameterName = parameterName;
        }

        public ScriptObjectNamedParameterBaseAttribute(string parameterName, string description, double[] numberListValues, string[] numberListNames) : this (parameterName, description, numberListValues, numberListNames, 0)
        {
        }
        public ScriptObjectNamedParameterBaseAttribute(string parameterName, string description, double[] numberListValues, string[] numberListNames, int displayOrder)
            : base(description, numberListValues, numberListNames, displayOrder)
        {
            _parameterName = parameterName;
        }
        public ScriptObjectNamedParameterBaseAttribute(string parameterName, string description, double[] numberListValues, string[] numberListNames, string displayTextArrayPropertyName) : this (parameterName, description, numberListValues, numberListNames, displayTextArrayPropertyName, 0)
        {
        }
        public ScriptObjectNamedParameterBaseAttribute(string parameterName, string description, double[] numberListValues, string[] numberListNames, string displayTextArrayPropertyName, int displayOrder)
            : base(description, numberListValues, numberListNames, displayOrder)
        {
            _parameterName = parameterName;
            _displayTextArrayPropertyName = displayTextArrayPropertyName;
        }

        public string ParameterName
        {
            get
            {
                return _parameterName;
            }
        }

        public string ParameterNameLowerCaseFirstCharacter
        {
            get
            {
                if (_parameterName.Length <= 1)
                    return _parameterName;
                else
                    return _parameterName[0].ToString().ToLower() + _parameterName.Substring(1);
            }
        }

        /// <summary>
        /// The name of the object property to retrieve the list of value display texts from.
        /// <example>For example the SetLightLevel method's LightID parameter would have it's DisplayTextArrayPropertyName set to LightNames.</example>
        /// </summary>
        public string DisplayTextArrayPropertyName
        {
            get
            {
                return _displayTextArrayPropertyName;
            }
        }
        public bool HasDisplayTextArrayPropertyName
        {
            get
            {
                return _displayTextArrayPropertyName != null;
            }
        }
        public string[] GetDisplayTextArray(IScriptObject obj)
        {
            IScriptObject resultScriptObject;
            ScriptObjectInvokeResult invokeResult = obj.GetProperty(_displayTextArrayPropertyName, out resultScriptObject);

            if (invokeResult == ScriptObjectInvokeResult.Success)
            {
                IScriptArray array = resultScriptObject as IScriptArray;
                if (array != null)
                {
                    if (array.PrimitiveBoundsLength == 0)
                        return new string[0];

                    string[] returnArray = new string[array.PrimitiveHighestIndex + 1];
                    for (int i = array.PrimitiveLowestIndex; i <= array.PrimitiveHighestIndex; i++)
                    {
                        IScriptObject so = array[i];
                        if (so != null)
                            returnArray[i] = so.ToPrimitiveObject().ToString();
                        else
                            returnArray[i] = "";
                    }

                    return returnArray;
                }
            }

            // TODO: should I be throwing an error if we get this far???

            return null;
        }
    }






    /// <summary>
    /// This attribute must be set on any field or property on an Script Object that can be accessed from a script.
    /// </summary>
    [Serializable]
    [System.AttributeUsage(System.AttributeTargets.Class)]
    public class ScriptObjectClassAttribute : ScriptObjectMemberBaseAttribute
    {
        public ScriptObjectClassAttribute(string classDisplayName, string description)
            :base   (classDisplayName, description, false)
        {
        }

        public ScriptObjectClassAttribute(string classDisplayName, string description, bool hide)
            : base(classDisplayName, description, hide)
        {
        }
    }

    









        /// <summary>
    /// This attribute must be set on any method on a Script Object that can be accessed from a script.
    /// </summary>
    [Serializable]
    [System.AttributeUsage(System.AttributeTargets.Constructor)]
    public class ScriptObjectConstructorAttribute : ScriptObjectMemberBaseAttribute
    {
        public ScriptObjectConstructorAttribute()
            : base("", "")
        {
        }

        public ScriptObjectConstructorAttribute(string displayName)
            : base(displayName, "")
        {
        }

        public ScriptObjectConstructorAttribute(string displayName, string description)
            :base (displayName, description)
        {
        }
    }

    /// <summary>
    /// This attribute describes the method parameters for a Script Object method that can be accessed from a script.
    /// </summary>
    [Serializable]
    [System.AttributeUsage(System.AttributeTargets.Constructor, AllowMultiple = true)]
    public class ScriptObjectConstructorParameterAttribute : ScriptObjectNamedParameterBaseAttribute
    {

        public ScriptObjectConstructorParameterAttribute(string parameterName, string description)
            : base (parameterName, description)
        {
        }

        public ScriptObjectConstructorParameterAttribute(string parameterName, string description, string mask)
            : base(parameterName, description, mask)
        {
        }

        public ScriptObjectConstructorParameterAttribute(string parameterName, string description, double minimumNumericValue, double maximumNumericValue)
            : base(parameterName, description, minimumNumericValue, maximumNumericValue)
        {
        }

        public ScriptObjectConstructorParameterAttribute(string parameterName, string description, string[] valueList)
            : base(parameterName, description, valueList)
        {
        }
        public ScriptObjectConstructorParameterAttribute(string parameterName, string description, double[] numberListValues, string[] numberListNames)
            : base(parameterName, description, numberListValues, numberListNames)
        {
        }
    }









    /// <summary>
    /// This attribute must be set on any field or property on an Script Object that can be accessed from a script.
    /// </summary>
    [Serializable]
    [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Field)]
    public class ScriptObjectPropertyAttribute : ScriptObjectNamedParameterBaseAttribute
    {
        private string _scriptBuilderGetText; // Ex: Light level for light number {index|0}.
        private string _scriptBuilderSetText; // Ex: Set light number {index|0}'s level to {value|99}.

        Type _arrayElementType;
        int _arrayMinIndex;
        int _arrayMaxIndex;

        public ScriptObjectPropertyAttribute()
            : base("", "")
        {
        }
        
        /// <summary>
        /// Initializes a new ScriptObjectProperty instance. This property will not be available in the Action List editor but can be used in scripts.
        /// </summary>
        /// <param name="propertyName">The display name for the property.</param>
        /// <param name="description">The description of the property.</param>
        public ScriptObjectPropertyAttribute(string propertyName, string description)
            : base(propertyName, description)
        {
        }

        /// <summary>
        /// Initializes a new ScriptObjectProperty instance with optional Action List editor support.
        /// </summary>
        /// <param name="propertyName">The display name for the property.</param>
        /// <param name="description">The description of the property.</param>
        /// <param name="scriptBuilderGetText">The text (with macros) to display in the Action List editor when Getting the property value. Set to null to prevent this property from being exposed in the Action List editor.</param>
        /// <param name="scriptBuilderSetText">The text (with macros) to display in the Action List editor when Setting the property value. Set to null to prevent this property from being exposed in the Action List editor, for example if there is no set accessor.</param>
        public ScriptObjectPropertyAttribute(string propertyName, string description, string scriptBuilderGetText, string scriptBuilderSetText)
            : base(propertyName, description)
        {
            _scriptBuilderGetText = scriptBuilderGetText;
            _scriptBuilderSetText = scriptBuilderSetText;
        }
        /// <summary>
        /// Initializes a new ScriptObjectProperty instance with optional Action List editor support and support for associating this ScriptArray property with another ScriptArray property that contains the names assocated with each element in this Script Array. Use this only if this property returns a ScriptArray (or object that inherits from ScriptArray) that contains all the same datatype and there is another property containing names to associate it with.
        /// </summary>
        /// <param name="propertyName">The display name for the property.</param>
        /// <param name="description">The description of the property.</param>
        /// <param name="scriptBuilderGetText">The text (with macros) to display in the Action List editor when Getting the property value. Set to null to prevent this property from being exposed in the Action List editor.</param>
        /// <param name="scriptBuilderSetText">The text (with macros) to display in the Action List editor when Setting the property value. Set to null to prevent this property from being exposed in the Action List editor, for example if there is no set accessor.</param>
        /// <param name="arrayElementType">The datatype for this ScriptArray property's elements. So for example if the script array holds all ScriptNumber's then you would specify: typeof(ScriptNumber).</param>
        /// <param name="arrayMinIndex">The lowest index in the array (usually 0 or 1).</param>
        /// <param name="arrayMaxIndex">The highest index in the array.</param>
        /// <param name="displayTextArrayPropertyName">The name of the property within this class that returns a ScriptArray (or a class that inherits from ScriptArray) that contains the names accociated with each value in this ScriptArray.</param>
        public ScriptObjectPropertyAttribute(string propertyName, string description, string scriptBuilderGetText, string scriptBuilderSetText, Type arrayElementType, int arrayMinIndex, int arrayMaxIndex, string displayTextArrayPropertyName)
            : base(propertyName, description)
        {
            _scriptBuilderGetText = scriptBuilderGetText;
            _scriptBuilderSetText = scriptBuilderSetText;

            _arrayElementType = arrayElementType;
            _arrayMinIndex = arrayMinIndex;
            _arrayMaxIndex = arrayMaxIndex;

            _displayTextArrayPropertyName = displayTextArrayPropertyName;
        }

        /// <summary>
        /// Initializes a new ScriptObjectProperty instance with an entry mask that can be used to ensure the value entered by a user conforms to a valid format. The property must be of type ScriptString. This property will not be available in the Action List editor but can be used in scripts.
        /// </summary>
        /// <param name="propertyName">The display name for the property.</param>
        /// <param name="description">The description of the property.</param>
        /// <param name="mask">The entry mask used to ensure the value entered by a user conforms to a valid format. Mask formats can be read about here: http://msdn.microsoft.com/en-us/library/system.windows.forms.maskedtextbox.mask.aspx</param>
        public ScriptObjectPropertyAttribute(string propertyName, string description, string mask)
            : base(propertyName, description, mask)
        {
        }

        /// <summary>
        /// Initializes a new ScriptObjectProperty instance with an entry mask that can be used to ensure the value entered by a user conforms to a valid format and optional Action List editor support.
        /// </summary>
        /// <param name="propertyName">The display name for the property.</param>
        /// <param name="description">The description of the property.</param>
        /// <param name="mask">The entry mask used to ensure the value entered by a user conforms to a valid format. Mask formats can be read about here: http://msdn.microsoft.com/en-us/library/system.windows.forms.maskedtextbox.mask.aspx</param>
        /// <param name="scriptBuilderGetText">The text (with macros) to display in the Action List editor when Getting the property value. Set to null to prevent this property from being exposed in the Action List editor.</param>
        /// <param name="scriptBuilderSetText">The text (with macros) to display in the Action List editor when Setting the property value. Set to null to prevent this property from being exposed in the Action List editor, for example if there is no set accessor.</param>
        public ScriptObjectPropertyAttribute(string propertyName, string description, string mask, string scriptBuilderGetText, string scriptBuilderSetText)
            : base(propertyName, description, mask)
        {
            _scriptBuilderGetText = scriptBuilderGetText;
            _scriptBuilderSetText = scriptBuilderSetText;
        }

        /// <summary>
        /// Initializes a new ScriptObjectProperty instance with a value range that is used to ensure the value entered by the user is within the allowed range. The property must be of type ScriptNumber. This property will not be available in the Action List editor but can be used in scripts.
        /// </summary>
        /// <param name="propertyName">The display name for the property.</param>
        /// <param name="description">The description of the property.</param>
        /// <param name="minimumNumericValue">The minimum numeric value for the property when the value is entered by the user in some interfaces. This is not enforced via the scripting language.</param>
        /// <param name="maximumNumericValue">The maximum numeric value for the property when the value is entered by the user in some interfaces. This is not enforced via the scripting language.</param>
        public ScriptObjectPropertyAttribute(string propertyName, string description, double minimumNumericValue, double maximumNumericValue)
            : base(propertyName, description, minimumNumericValue, maximumNumericValue)
        {
        }
        
        /// <summary>
        /// Initializes a new ScriptObjectProperty instance with a value range that is used to ensure the value entered by the user is within the allowed range and optional Action List editor support. The property must be of type ScriptNumber.
        /// </summary>
        /// <param name="propertyName">The display name for the property.</param>
        /// <param name="description">The description of the property.</param>
        /// <param name="minimumNumericValue">The minimum numeric value for the property when the value is entered by the user in some interfaces. This is not enforced via the scripting language.</param>
        /// <param name="maximumNumericValue">The maximum numeric value for the property when the value is entered by the user in some interfaces. This is not enforced via the scripting language.</param>
        /// <param name="scriptBuilderGetText">The text (with macros) to display in the Action List editor when Getting the property value. Set to null to prevent this property from being exposed in the Action List editor.</param>
        /// <param name="scriptBuilderSetText">The text (with macros) to display in the Action List editor when Setting the property value. Set to null to prevent this property from being exposed in the Action List editor, for example if there is no set accessor.</param>
        public ScriptObjectPropertyAttribute(string propertyName, string description, double minimumNumericValue, double maximumNumericValue, string scriptBuilderGetText, string scriptBuilderSetText)
            : base(propertyName, description, minimumNumericValue, maximumNumericValue)
        {
            _scriptBuilderGetText = scriptBuilderGetText;
            _scriptBuilderSetText = scriptBuilderSetText;
        }

        /// <summary>
        /// Initializes a new ScriptObjectProperty instance with a value range that is used to ensure the value entered by the user is within the allowed range and optional Action List editor support and support for associating this ScriptArray property with another ScriptArray property that contains the names assocated with each element in this Script Array. Use this only if this property returns a ScriptArray (or object that inherits from ScriptArray) that contains all ScriptNumber values and there is another property containing names to associate it with.
        /// </summary>
        /// <param name="propertyName">The display name for the property.</param>
        /// <param name="description">The description of the property.</param>
        /// <param name="minimumNumericValue">The minimum numeric value for the property when the value is entered by the user in some interfaces. This is not enforced via the scripting language.</param>
        /// <param name="maximumNumericValue">The maximum numeric value for the property when the value is entered by the user in some interfaces. This is not enforced via the scripting language.</param>
        /// <param name="scriptBuilderGetText">The text (with macros) to display in the Action List editor when Getting the property value. Set to null to prevent this property from being exposed in the Action List editor.</param>
        /// <param name="scriptBuilderSetText">The text (with macros) to display in the Action List editor when Setting the property value. Set to null to prevent this property from being exposed in the Action List editor, for example if there is no set accessor.</param>
        /// <param name="arrayElementType">The datatype for this ScriptArray property's elements. So for example if the script array holds all ScriptNumber's then you would specify: typeof(ScriptNumber).</param>
        /// <param name="arrayMinIndex">The lowest index in the array (usually 0 or 1).</param>
        /// <param name="arrayMaxIndex">The highest index in the array.</param>
        /// <param name="displayTextArrayPropertyName">The name of the property within this class that returns a ScriptArray (or a class that inherits from ScriptArray) that contains the names accociated with each value in this ScriptArray.</param>
        public ScriptObjectPropertyAttribute(string propertyName, string description, double minimumNumericValue, double maximumNumericValue, string scriptBuilderGetText, string scriptBuilderSetText, Type arrayElementType, int arrayMinIndex, int arrayMaxIndex, string displayTextArrayPropertyName)
            : base(propertyName, description, minimumNumericValue, maximumNumericValue, displayTextArrayPropertyName)
        {
            _scriptBuilderGetText = scriptBuilderGetText;
            _scriptBuilderSetText = scriptBuilderSetText;

            _arrayElementType = arrayElementType;
            _arrayMinIndex = arrayMinIndex;
            _arrayMaxIndex = arrayMaxIndex;
        }

        /// <summary>
        /// Initializes a new ScriptObjectProperty instance with a list of allowed string values that the user sets the property to a valid value. The property must be of type ScriptString. This property will not be available in the Action List editor but can be used in scripts.
        /// </summary>
        /// <param name="propertyName">The display name for the property.</param>
        /// <param name="description">The description of the property.</param>
        /// <param name="valueList">A list of allowed string values that the user can choose from in some interface. This is not enforced via the scripting language.</param>
        public ScriptObjectPropertyAttribute(string propertyName, string description, string[] valueList)
            : base(propertyName, description, valueList)
        {
        }

        /// <summary>
        /// Initializes a new ScriptObjectProperty instance with a list of allowed string values that the user sets the property to a valid value and optional Action List editor support.  The property must be of type ScriptString.
        /// </summary>
        /// <param name="propertyName">The display name for the property.</param>
        /// <param name="description">The description of the property.</param>
        /// <param name="valueList">A list of allowed string values that the user can choose from in some interface. This is not enforced via the scripting language.</param>
        /// <param name="scriptBuilderGetText">The text (with macros) to display in the Action List editor when Getting the property value. Set to null to prevent this property from being exposed in the Action List editor.</param>
        /// <param name="scriptBuilderSetText">The text (with macros) to display in the Action List editor when Setting the property value. Set to null to prevent this property from being exposed in the Action List editor, for example if there is no set accessor.</param>
        public ScriptObjectPropertyAttribute(string propertyName, string description, string[] valueList, string scriptBuilderGetText, string scriptBuilderSetText)
            : base(propertyName, description, valueList)
        {
            _scriptBuilderGetText = scriptBuilderGetText;
            _scriptBuilderSetText = scriptBuilderSetText;
        }
        
        /// <summary>
        /// Initializes a new ScriptObjectProperty instance with a list of number and string values used to ensure the value entered by the user is a valid value. The property must be of type ScriptNumber. This property will not be available in the Action List editor but can be used in scripts.
        /// </summary>
        /// <param name="propertyName">The display name for the property.</param>
        /// <param name="description">The description of the property.</param>
        /// <param name="numberListValues">A list of numeric values which the property can be set to.  This is not enforced via the scripting language.</param>
        /// <param name="numberListNames">A list of display names associated with the numeric values list. This is used when diplaying the value in some interfaces.</param>
        public ScriptObjectPropertyAttribute(string propertyName, string description, double[] numberListValues, string[] numberListNames)
            : base(propertyName, description, numberListValues, numberListNames)
        {
        }

        /// <summary>
        /// Initializes a new ScriptObjectProperty instance with a list of number and string values used to ensure the value entered by the user is a valid value and optional Action List editor support. The property must be of type ScriptNumber.
        /// </summary>
        /// <param name="propertyName">The display name for the property.</param>
        /// <param name="description">The description of the property.</param>
        /// <param name="numberListValues">A list of numeric values which the property can be set to.  This is not enforced via the scripting language.</param>
        /// <param name="numberListNames">A list of display names associated with the numeric values list. This is used when diplaying the value in some interfaces.</param>
        /// <param name="scriptBuilderGetText">The text (with macros) to display in the Action List editor when Getting the property value. Set to null to prevent this property from being exposed in the Action List editor.</param>
        /// <param name="scriptBuilderSetText">The text (with macros) to display in the Action List editor when Setting the property value. Set to null to prevent this property from being exposed in the Action List editor, for example if there is no set accessor.</param>
        public ScriptObjectPropertyAttribute(string propertyName, string description, double[] numberListValues, string[] numberListNames, string scriptBuilderGetText, string scriptBuilderSetText)
            : base(propertyName, description, numberListValues, numberListNames)
        {
            _scriptBuilderGetText = scriptBuilderGetText;
            _scriptBuilderSetText = scriptBuilderSetText;
        }

        /// <summary>
        /// Initializes a new ScriptObjectProperty instance with a list of number and string values used to ensure the value entered by the user is a valid value and optional Action List editor support and support for associating this ScriptArray property with another ScriptArray property that contains the names assocated with each element in this Script Array. Use this only if this property returns a ScriptArray (or object that inherits from ScriptArray) that contains all ScriptNumber values and there is another property containing names to associate it with.
        /// </summary>
        /// <param name="propertyName">The display name for the property.</param>
        /// <param name="description">The description of the property.</param>
        /// <param name="numberListValues">A list of numeric values which the property can be set to.  This is not enforced via the scripting language.</param>
        /// <param name="numberListNames">A list of display names associated with the numeric values list. This is used when diplaying the value in some interfaces.</param>
        /// <param name="scriptBuilderGetText">The text (with macros) to display in the Action List editor when Getting the property value. Set to null to prevent this property from being exposed in the Action List editor.</param>
        /// <param name="scriptBuilderSetText">The text (with macros) to display in the Action List editor when Setting the property value. Set to null to prevent this property from being exposed in the Action List editor, for example if there is no set accessor.</param>
        /// <param name="arrayElementType">The datatype for this ScriptArray property's elements. So for example if the script array holds all ScriptNumber's then you would specify: typeof(ScriptNumber).</param>
        /// <param name="arrayMinIndex">The lowest index in the array (usually 0 or 1).</param>
        /// <param name="arrayMaxIndex">The highest index in the array.</param>
        /// <param name="displayTextArrayPropertyName">The name of the property within this class that returns a ScriptArray (or a class that inherits from ScriptArray) that contains the names accociated with each value in this ScriptArray.</param>
        public ScriptObjectPropertyAttribute(string propertyName, string description, double[] numberListValues, string[] numberListNames, string scriptBuilderGetText, string scriptBuilderSetText, Type arrayElementType, int arrayMinIndex, int arrayMaxIndex, string displayTextArrayPropertyName)
            : base(propertyName, description, numberListValues, numberListNames, displayTextArrayPropertyName)
        {
            _scriptBuilderGetText = scriptBuilderGetText;
            _scriptBuilderSetText = scriptBuilderSetText;

            _arrayElementType = arrayElementType;
            _arrayMinIndex = arrayMinIndex;
            _arrayMaxIndex = arrayMaxIndex;
        }

        public string ScriptBuilderGetText
        {
            get
            {
                return _scriptBuilderGetText;
            }
        }

        public string ScriptBuilderSetText
        {
            get
            {
                return _scriptBuilderSetText;
            }
        }

        public static void ValidateScriptBuilderTexts(Type t)
        {
            foreach (PropertyInfo pi in t.GetProperties(BindingFlags.Instance | BindingFlags.Public))
            {
                ScriptObjectPropertyAttribute[] attribs =  (ScriptObjectPropertyAttribute[])pi.GetCustomAttributes(typeof(ScriptObjectPropertyAttribute), false);
                if (attribs.Length > 0)
                    attribs[0].ValidateScriptBuilderText(pi);
            }
        }
        public void ValidateScriptBuilderText(PropertyInfo pi)
        {
            // Validate scriptBuilderText for macro errors.

            string[] items = new string[]{ _scriptBuilderGetText, _scriptBuilderSetText};
            
            // Syntax: {INDEX|<zero based index>} OR {VALUE|<defaultValue>|[additional parms]} OR {NAME}
            // The only additional params are for boolean true|false text.

            // Examples:
            // Property Set:  Set light number {INDEX|0}'s level to {VALUE|99}.
            // Property Get:  Light level for light number {INDEX|0}.
            // {VALUE|false|on|off}

            foreach (string s in items)
            {
                if (s == null)
                    continue;

                int bracketStart = -1;
                int pos = 0;
                while (pos < s.Length)
                {
                    if (s[pos] == '{')
                    {
                        // Check if the bracket is escaped.
                        if (pos < s.Length - 1 && s[pos + 1] == '{')
                            pos++; // skip over the 1st bracket
                        else if (bracketStart != -1)
                            throw new FormatException("The " + pi.Name + " ScriptObjectProperty attribute's scriptBuilderText parameter has an invalid format, nested '{'.");
                        bracketStart = pos;
                    }
                    else if (s[pos] == '}')
                    {
                        if (bracketStart == -1)
                            throw new FormatException("The " + pi.Name + " ScriptObjectProperty attribute's scriptBuilderText parameter has an invalid format, missing '{'.");

                        string macro = s.Substring(bracketStart + 1, pos - bracketStart - 1);
                        string[] macroParts = macro.Split('|');

                        if (macroParts[0].ToUpper() == "NAME" && macroParts.Length > 1)
                            throw new FormatException("The " + pi.Name + " ScriptObjectProperty attribute's scriptBuilderText parameter has an invalid format, NAME macros must have 1 parameter. Ex: {NAME}.");

                        if (macroParts[0].ToUpper() == "INDEX" && macroParts.Length != 2)
                            throw new FormatException("The " + pi.Name + " ScriptObjectProperty attribute's scriptBuilderText parameter has an invalid format, INDEX macros must have 2 parameters. Ex: {INDEX|<index>}.");

                        if (macroParts[0].ToUpper() == "VALUE")
                        {
                            if (pi.PropertyType == typeof(ScriptBoolean) || (ScriptObject.IsScriptArray(pi.PropertyType) && this.ArrayElementType == typeof(ScriptBoolean)))
                            {
                                if (macroParts.Length != 4)
                                    throw new FormatException("The " + pi.Name + " ScriptObjectProperty attribute's scriptBuilderText parameter has an invalid format, VALUE macros for ScriptBoolean types must have 4 parameters. Ex: {VALUE|false|on|off}.");

                                bool defaultValue;
                                if (bool.TryParse(macroParts[1], out defaultValue) == false)
                                    throw new FormatException("The " + pi.Name + " ScriptObjectProperty attribute's scriptBuilderText parameter has an invalid format, The 2nd VALUE macro parameter for ScriptBoolean types must be true or false. Ex: {VALUE|false|on|off}.");
                            }
                            else if (ScriptObject.IsScriptArray(pi.PropertyType))
                            {
                                // we don't know the element type
                                if (macroParts.Length == 4)
                                    throw new FormatException("The " + pi.Name + " ScriptObjectProperty attribute's scriptBuilderText has 4 parameters (which indicated that it is for boolean array elements but the attribute's arrayElementType parameter is not set to ScriptBoolean. Please fix the PARAM macro or set the arrayElementType parameter to ScriptBoolean.");
                                else if (macroParts.Length != 2)
                                    throw new FormatException("The " + pi.Name + " ScriptObjectProperty attribute's scriptBuilderText parameter has an invalid format, VALUE macro must have 2 parameters for ScriptArray properties with not element type specified. Ex: {VALUE|99}.");
                            }
                            else if (macroParts.Length != 2)
                                throw new FormatException("The " + pi.Name + " ScriptObjectProperty attribute's scriptBuilderText parameter has an invalid format, VALUE macro must have 2 parameters for non-boolean types. Ex: {VALUE|99}.");
                        }

                        if (macroParts[0].ToUpper() != "VALUE" && macroParts[0].ToUpper() != "INDEX" && macroParts[0].ToUpper() != "NAME")
                            throw new FormatException("The " + pi.Name + " ScriptObjectProperty attribute's scriptBuilderText parameter has an invalid format, macro has unknown macro type '" + macroParts[0] + "'.");

                        bracketStart = -1;
                    }

                    pos++;
                }

                if (bracketStart != -1)
                    throw new FormatException("The " + pi.Name + " ScriptObjectProperty attribute's scriptBuilderText parameter has an invalid format, missing '}'.");
            }
        }

        public bool HasArrayInformation
        {
            get
            {
                return _arrayElementType != null;
            }
        }

        public Type ArrayElementType
        {
            get { return _arrayElementType; }
        }
        public int ArrayMinIndex
        {
            get { return _arrayMinIndex; }
        }
        public int ArrayMaxIndex
        {
            get { return _arrayMaxIndex; }
        }
    }


    /// <summary>
    /// This attribute must be set on any method on a Script Object that can be accessed from a script.
    /// </summary>
    [Serializable]
    [System.AttributeUsage(System.AttributeTargets.Method | System.AttributeTargets.Property | System.AttributeTargets.Field)]
    public class ScriptObjectMethodAttribute : ScriptObjectMemberBaseAttribute
    {
        private string _scriptBuilderText;

        public ScriptObjectMethodAttribute()
            : base("", "")
        {
        }

        /// <summary>
        /// Initializes a new ScriptObjectMethod instance. This method will not be available in the Action List editor but can be used in scripts.
        /// </summary>
        /// <param name="displayName">The displayname of the method.</param>
        public ScriptObjectMethodAttribute(string displayName)
            : base(displayName, "")
        {
        }

        /// <summary>
        /// Initializes a new ScriptObjectMethod instance. This method will not be available in the Action List editor but can be used in scripts.
        /// </summary>
        /// <param name="displayName">The display name of the method.</param>
        /// <param name="description">The description of the method.</param>
        public ScriptObjectMethodAttribute(string displayName, string description)
            : base(displayName, description)
        {
        }

                /// <summary>
        /// Initializes a new ScriptObjectMethod instance with Action List editor support.
        /// </summary>
        /// <param name="displayName">The display name of the method.</param>
        /// <param name="description">The description of the method.</param>
        /// <param name="scriptBuilderText">The text (with macros) to display in the Action List editor when invoking this method.</param>
        public ScriptObjectMethodAttribute(string displayName, string description, string scriptBuilderText)
            : base(displayName, description)
        {
            _scriptBuilderText = scriptBuilderText;
        }

        public static void ValidateScriptBuilderText(Type t)
        {
            foreach (MethodInfo mi in t.GetMethods(BindingFlags.Instance | BindingFlags.Public))
            {
                ScriptObjectMethodAttribute[] attribs = (ScriptObjectMethodAttribute[])mi.GetCustomAttributes(typeof(ScriptObjectMethodAttribute), false);
                if (attribs.Length > 0)
                    attribs[0].ValidateScriptBuilderText(mi);
            }
        }
        public void ValidateScriptBuilderText(MethodInfo mi)
        {
            if (_scriptBuilderText == null)
                return;

            Hashtable paramIndexes = new Hashtable();
            ParameterInfo[] mparams = mi.GetParameters();

            // Validate scriptBuilderText for macro errors.
            // Example: "Set the area {param|0|1} keypad text line 1 to {param|3|Line 1} and line 2 to {param|4|Line2} for {param|2|10} seconds {param|1|false|with a beep|without a beep}.")]
            // Syntax: {param|<zero based index>|<defaultValue>|[additional parms]}
            // Syntax: {name}
            // The only additional params are for boolean true|false text.
            int bracketStart = -1;
            int pos = 0;
            while (pos < _scriptBuilderText.Length)
            {
                if (_scriptBuilderText[pos] == '{')
                {
                    // Check if the bracket is escaped.
                    if (pos < _scriptBuilderText.Length - 1 && _scriptBuilderText[pos + 1] == '{')
                        pos++; // skip over the 1st bracket
                    else if (bracketStart != -1)
                        throw new FormatException("The '" + mi.Name + "' ScriptObjectMethod attribute's scriptBuilderText parameter has an invalid format, nested '{'.");
                    else
                        bracketStart = pos; // found start non-escaped bracket
                }
                else if (_scriptBuilderText[pos] == '}')
                {
                    // Check if the bracket is escaped.
                    if (pos < _scriptBuilderText.Length - 1 && _scriptBuilderText[pos + 1] == '}')
                    {
                        pos++; // skip over the 1st bracket
                    }
                    else if (bracketStart == -1)
                    {
                        throw new FormatException("The '" + mi.Name + "' ScriptObjectMethod attribute's scriptBuilderText parameter has an invalid format, missing '{'.");
                    }
                    else
                    {
                        // Found non-escaped closing bracket

                        string macro = _scriptBuilderText.Substring(bracketStart + 1, pos - bracketStart - 1);
                        string[] macroParts = macro.Split('|');

                        if (macroParts[0].ToUpper() == "NAME" && macroParts.Length > 1)
                            throw new FormatException("The " + mi.Name + " ScriptObjectMethod attribute's scriptBuilderText parameter has an invalid format, NAME macros must have 1 parameter. Ex: {NAME}.");
                        
                        if (macroParts[0].ToUpper() == "PARAM")
                        {
                            if (macroParts.Length < 2)
                                throw new FormatException("The " + mi.Name + " ScriptObjectMethod attribute's scriptBuilderText parameter has an invalid format, the 2nd PARAM macro parameter must be the numeric index of a method parameter (base 1). Ex: {PARAM|1|foo}.");

                            int paramIndex;
                            if (int.TryParse(macroParts[1], out paramIndex) == false)
                                throw new FormatException("The " + mi.Name + " ScriptObjectMethod attribute's scriptBuilderText parameter has an invalid format, the 2nd PARAM macro parameter must be the numeric index of a method parameter (base 1). Ex: {PARAM|1|foo}.");

                            if (paramIndex < 0 || paramIndex >= mparams.Length)
                                throw new FormatException("The " + mi.Name + " ScriptObjectMethod attribute's scriptBuilderText parameter has an invalid format, the PARAM macro parameter's method parameter index value is range for the number of method parameters. Ex: {PARAM|1|foo}.");

                            if (paramIndexes.ContainsKey(paramIndex))
                                throw new FormatException("The " + mi.Name + " ScriptObjectMethod attribute's scriptBuilderText parameter has more than one PARAM macro which specifies method parameter index " + paramIndex + ".");
                            paramIndexes.Add(paramIndex, null);

                            if (mparams[paramIndex].ParameterType == typeof(ScriptBoolean))
                            {
                                if (macroParts.Length != 5)
                                    throw new FormatException("The " + mi.Name + " ScriptObjectMethod attribute's scriptBuilderText parameter has an invalid format, PARAM macros for ScriptBoolean types must have 5 parameters. Ex: {param|1|false|on|offp}.");

                                bool defaultValue;
                                if (bool.TryParse(macroParts[2], out defaultValue) == false)
                                    throw new FormatException("The " + mi.Name + " ScriptObjectMethod attribute's scriptBuilderText parameter has an invalid format, The 3rd PARAM macro parameter for ScriptBoolean types must be true or false. Ex: {PARAM|1|false|on|off}.");
                            }
                            else
                            {
                                if (macroParts.Length != 3)
                                    throw new FormatException("The " + mi.Name + " ScriptObjectMethod attribute's scriptBuilderText parameter has an invalid format, PARAM macros must have 3 parameters. Ex: {PARAM|1|99}.");
                            }
                        }
                        if (macroParts[0].ToUpper() != "PARAM" && macroParts[0].ToUpper() != "NAME")
                            throw new FormatException("The '" + mi.Name + "' ScriptObjectMethod attribute's scriptBuilderText parameter has an invalid format, macro has unknown macro type '" + macroParts[0] + "'.");

                        bracketStart = -1;
                    }
                }

                pos++;
            }

            if (bracketStart != -1)
                throw new FormatException("The " + mi.Name + " ScriptObjectMethod attribute's scriptBuilderText parameter has an invalid format, missing '}'.");

            if (paramIndexes.Count != mparams.Length)
                throw new FormatException("The '" + mi.Name + "' ScriptObjectMethod attribute's scriptBuilderText parameter is missing a PARAM macro for one of the method parameters.");
        }

        public string ScriptBuilderText
        {
            get
            {
                return _scriptBuilderText;
            }
        }
    }

    /// <summary>
    /// This attribute describes the method parameters for a Script Object method that can be accessed from a script.
    /// </summary>
    [Serializable]
    [System.AttributeUsage(System.AttributeTargets.Method, AllowMultiple = true)]
    public class ScriptObjectMethodParameterAttribute : ScriptObjectNamedParameterBaseAttribute
    {
        public ScriptObjectMethodParameterAttribute(string parameterName, string description)
            : base (parameterName, description)
        {
        }

        public ScriptObjectMethodParameterAttribute(string parameterName, string description, string mask)
            : base(parameterName, description, mask)
        {
        }

        public ScriptObjectMethodParameterAttribute(string parameterName, string description, double minimumNumericValue, double maximumNumericValue)
            : base(parameterName, description, minimumNumericValue, maximumNumericValue)
        {
        }

        public ScriptObjectMethodParameterAttribute(string parameterName, string description, double minimumNumericValue, double maximumNumericValue, string displayTextArrayPropertyName)
            : base(parameterName, description, minimumNumericValue, maximumNumericValue, displayTextArrayPropertyName)
        {
        }

        public ScriptObjectMethodParameterAttribute(string parameterName, string description, string[] valueList)
            : base(parameterName, description, valueList)
        {
        }

        public ScriptObjectMethodParameterAttribute(string parameterName, string description, double[] numberListValues, string[] numberListNames)
            : base(parameterName, description, numberListValues, numberListNames)
        {
        }
    }

    [Serializable]
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true)]
    public class ScriptObjectExampleAttribute : Attribute
    {
        public string Name { get; set; } // the name/title. Defaults to Example.
        public string Description { get; set; } // the description is usually shown above the example script
        public string ExampleScript { get; set; } // the actual j9Script example
        public int DisplayOrder { get; set; } // since multiple attributes are allowed this indicates the order

        public ScriptObjectExampleAttribute(string description, string exampleScript, int displayOrder) : this ("Example", description, exampleScript, displayOrder)
        {
        }

        public ScriptObjectExampleAttribute(string name, string description, string exampleScript, int displayOrder)
        {
            this.Name = name;
            this.Description = description;
            this.ExampleScript = exampleScript;
            this.DisplayOrder = displayOrder;
        }
    }
    #endregion



    #region MemberInfos

    [Serializable]
    public class ScriptObjectConstructorInfo
    {
        public ScriptObjectConstructorAttribute Attribute;
        public ScriptObjectConstructorParameterAttribute[] ParameterAttributes;
        public Type[] ParameterTypes;

        //public override string ToString()
        //{
        //    if (Attribute != null && !string.IsNullOrEmpty(Attribute.DisplayName))  // The attribute should never be null... but just in case the attribute display name is null or blank.
        //        return Attribute.DisplayName;
        //    else
        //        return MethodName;
        //}
    }

    [Serializable]
    public class ScriptObjectPropertyInfo
    {
        public string PropertyName;
        public bool CanRead;
        public bool CanWrite;
        public ScriptObjectPropertyAttribute Attribute;
        public string AssemblyQualifiedName;
        public string FullTypeName; // this is the full type name (including namespace), even if it is a type that the caller does not have loaded/access to.
        public Type KnownType;  // this is the base known type, like ScriptString, or ScriptNumber.
        public bool IsReadStatic;
        public bool IsWriteStatic;
        public bool IsObsolete;
        public bool IsClassMember; // indicates if the property is a real class property or is a dynamic property.

        public override string ToString()
        {
            if (Attribute != null && !string.IsNullOrEmpty(Attribute.ParameterName))  // The attribute should never be null... but just in case the attribute display name is null or blank.
                return Attribute.ParameterName;
            else
                return PropertyName;
        }

        public string TypeName // typename without the namespace
        {
            get
            {
                int pos = FullTypeName.LastIndexOf('.');
                if (pos > -1)
                    return FullTypeName.Substring(pos + 1);
                else
                    return FullTypeName;
            }
        }

        public bool IsArray
        {
            get
            {
                return ScriptObject.IsScriptArray(KnownType);
            }
        }

    }


    [Serializable]
    public class ScriptObjectMethodInfo
    {
        public string MethodName;
        public ScriptObjectMethodAttribute Attribute;
        public ScriptObjectMethodParameterAttribute[] ParameterAttributes;
        public Type[] ParameterTypes;
        public string AssemblyQualifiedName;
        public string FullReturnTypeName; // this is the full type name, even if it is a type that the caller does not have loaded/access to.
        public Type KnownReturnType;  // this is the base known type, like ScriptString, or ScriptNumber.
        public bool IsStatic;
        public bool IsObsolete;

        public string ShortReturnTypeName
        {
            get
            {
                string typename = FullReturnTypeName;

                // Get the name after the last '.'
                int pos = typename.LastIndexOf('.');
                if (pos > -1)
                    typename = typename.Substring(pos + 1);

                // Strip off "Script" from the beginning... so "ScriptString" will be just "String".
                if (typename.StartsWith("Script"))
                    typename = typename.Substring(6);  // Strip off "Script" prefix.

                return typename;
            }
        }

        public override string ToString()
        {
            if (Attribute != null && !string.IsNullOrEmpty(Attribute.DisplayName))  // The attribute should never be null... but just in case the attribute display name is null or blank.
                return Attribute.DisplayName;
            else
                return MethodName;
        }
    }

    #endregion






    public enum ScriptObjectInvokeResult
    {
        Success,
        UndefinedMember,
        MissingScriptMethodAttribute,
        MissingScriptPropertyAttribute,
        IsReadOnly,
        IsWriteOnly,
        InvalidType,
        WrongNumberOfArguments
    }

    public interface IScriptObject
    {
        object Clone();
        ScriptObjectInvokeResult GetIndexer(int index, out IScriptObject result);
        ScriptObjectInvokeResult GetProperty(string propertyName, out IScriptObject result);
        ScriptObjectInvokeResult GetProperty(string propertyName, object[] index, out IScriptObject result);
        ScriptObjectInvokeResult InvokeMethod(string methodName, IScriptObject[] parameters, out IScriptObject result);
        System.Collections.Generic.List<ScriptObjectConstructorInfo> ScriptObjectConstructors { get; }
        string ScriptObjectDescription { get; }
        string ScriptObjectDisplayName { get; }
        System.Collections.Generic.List<ScriptObjectMethodInfo> ScriptObjectMethods { get; }
        System.Collections.Generic.Dictionary<string, ScriptObjectPropertyInfo> ScriptObjectProperties { get; }
        byte[] Serialize();
        ScriptObjectInvokeResult SetIndexer(int index, IScriptObject value);
        ScriptObjectInvokeResult SetProperty(string propertyName, IScriptObject value);
        ScriptObjectInvokeResult SetProperty(string propertyName, IScriptObject value, object[] index);
        string ToDisplayString(ScriptObjectPropertyAttribute attribute);
        bool ToPrimitiveBoolean();
        double ToPrimitiveDouble();
        int ToPrimitiveInt32();
        object ToPrimitiveObject();
        string ToPrimitiveString();
        string TypeName { get; }
    }

    public interface ISerialize
    {
        byte[] Serialize();
    }

    /// <summary>
    /// All expression evalator objects must inherit from this class.
    /// </summary>
    [Serializable]
    [ScriptObjectClass("Object", "This is the base script object class which all other script accessible objects inherit.  It can not be instantiated from a script.")]
    public abstract class ScriptMarshalByRefObject : MarshalByRefObject, IScriptObject, ISerializable, ISerialize, ICloneable
    {
        public ScriptMarshalByRefObject()
        {
        }

        public virtual string TypeName
        {
            get { return ScriptObject.GetShortTypeName(this.GetType().Name); }
        }
        
        public virtual string ScriptObjectDescription
        {
            get { return ScriptObject.GetScriptObjectDescription(this.GetType()); }
        }

        public virtual string ScriptObjectDisplayName
        {
            get { return ScriptObject.GetScriptObjectDisplayName(this.GetType()); }
        }

        private List<ScriptObjectConstructorInfo> _scriptObjectConstructorsCache = null;
        public virtual List<ScriptObjectConstructorInfo> ScriptObjectConstructors
        {
            get
            {
                if (_scriptObjectConstructorsCache == null)
                    _scriptObjectConstructorsCache = ScriptObject.GetScriptObjectConstructors(this.GetType());

                // Return a NEW list so that the caller can't manipulate the dictionary item count, etc. The list items are not cloned.
                return _scriptObjectConstructorsCache;
            }
        }

        private List<ScriptObjectMethodInfo> _scriptObjectMethodsCache = null;
        public virtual List<ScriptObjectMethodInfo> ScriptObjectMethods
        {
            get
            {
                if (_scriptObjectMethodsCache==null)
                    _scriptObjectMethodsCache = ScriptObject.GetScriptObjectMethods(this.GetType());

                // Return a NEW list so that the caller can't manipulate the dictionary item count, etc. The list items are not cloned.
                return new List<ScriptObjectMethodInfo>(_scriptObjectMethodsCache);
            }
        }

        private Dictionary<string, ScriptObjectPropertyInfo> _scriptObjectPropertiesCache = null;
        public virtual Dictionary<string, ScriptObjectPropertyInfo> ScriptObjectProperties
        {
            get
            {
                if (_scriptObjectPropertiesCache == null)
                    _scriptObjectPropertiesCache = ScriptObject.GetScriptObjectProperties(this.GetType());
                
                // Return a NEW dictionary so that the caller can't manipulate the dictionary item count, etc. The dictionary items are not cloned.
                return new Dictionary<string,ScriptObjectPropertyInfo>(_scriptObjectPropertiesCache);
            }
        }


        public virtual object Clone()
        {
            return ScriptObject.Clone(this);
        }

        /// <summary>
        /// Serializes the object to a byte array.
        /// </summary>
        public virtual byte[] Serialize()
        {
            return ScriptObject.Serialize(this);
        }

        public virtual ScriptObjectInvokeResult GetProperty(string propertyName, out IScriptObject result)
        {
            return ScriptObject.GetProperty(this, propertyName, out result);
        }

        public virtual ScriptObjectInvokeResult GetProperty(string propertyName, object[] index, out IScriptObject result)
        {
            return ScriptObject.GetProperty(this, propertyName, index, out result);
        }

        public virtual ScriptObjectInvokeResult SetProperty(string propertyName, IScriptObject value)
        {
            return ScriptObject.SetProperty(this, propertyName, value, null);
        }

        public virtual ScriptObjectInvokeResult SetProperty(string propertyName, IScriptObject value, object[] index)
        {
            return ScriptObject.SetProperty(this, propertyName, value, index);
        }

        public virtual ScriptObjectInvokeResult GetIndexer(int index, out IScriptObject result)
        {
            return ScriptObject.GetIndexer(this, index, out result);
        }

        public virtual ScriptObjectInvokeResult SetIndexer(int index, IScriptObject value)
        {
            return ScriptObject.SetIndexer(this, index, value);
        }
        
        public virtual ScriptObjectInvokeResult InvokeMethod(string methodName, IScriptObject[] parameters, out IScriptObject result)
        {
            return ScriptObject.InvokeMethod(this, methodName, parameters, out result);
        }

        protected static bool IsNumericDataType(System.Type type)
        {
            if (type == typeof(System.Decimal) ||
                type == typeof(System.Single) ||
                type == typeof(System.Double) ||
                type == typeof(System.Int16) ||
                type == typeof(System.Int32) ||
                type == typeof(System.Int64) ||
                type == typeof(System.UInt16) ||
                type == typeof(System.UInt32) ||
                type == typeof(System.UInt64) ||
                type == typeof(System.Byte) ||
                type == typeof(System.SByte))
                return true;

            return false;
        }

        public virtual string ToPrimitiveString() // for internal use, not for use in scripts.
        {
            return this.ToString();
        }
        public virtual Int32 ToPrimitiveInt32() // for internal use, not for use in scripts.
        {
            throw new Exception(this.TypeName + " can not be converted to a Number datatype.");
        }
        public virtual bool ToPrimitiveBoolean() // for internal use, not for use in scripts.
        {
            throw new Exception(this.TypeName + " can not be converted to a Boolean datatype.");
        }

        public virtual double ToPrimitiveDouble()
        {
            throw new Exception(this.TypeName + " can not be converted to a Number datatype.");
        }

        public virtual object ToPrimitiveObject() // for internal use, not for use in scripts.
        {
            throw new Exception(this.TypeName + " can not be converted to a primitive .net datatype.");
        }
        
        public override object InitializeLifetimeService()
        {
            //OLD: Prevent the remoting connection to this object from timing out after 5 minutes so that the object is always available to the client.
            //return null; // the remoting lease should never expire.

            // Return the defaults... which I think are 5 mins initial time, then 2 minute renewals. The scripting engine has an ISponsor which keeps the objects alive while the script is running if the object exists on a remote host.
            System.Runtime.Remoting.Lifetime.ILease lease = (System.Runtime.Remoting.Lifetime.ILease)base.InitializeLifetimeService();
            return lease;
        }


        public virtual string ToDisplayString(ScriptObjectPropertyAttribute attribute)
        {
            return ScriptObject.ToDisplayString(this, attribute);
        }



        //public static explicit operator string(IScriptObject value)
        //{
        //    ????
        //}

        #region ISerializable Members
        //protected IScriptObject(SerializationInfo info, StreamingContext context)
        //    : base(info, context)
        //{ 
        //}
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("o", this, this.GetType());
        }
        #endregion
    }


    [Serializable]
    [ScriptObjectClass("Exception", "A script exception.")]
    public class ScriptException : ScriptObject, ISerializable
    {
        // ECMAScript properties:
        //   private string _message;
        //   private string _name;
        //     EvalError: raised when an error occurs executing code in eval()
        //     RangeError: raised when a numeric variable or parameter is outside of its valid range
        //     ReferenceError: raised when de-referencing an invalid reference
        //     SyntaxError: raised when a syntax error occurs while parsing code in eval()
        //     TypeError: raised when a variable or parameter is not a valid type
        //     URIError: raised when encodeURI() or decodeURI() are passed invalid parameters

        private Exception _actualException;

        // for internal use
        public ScriptException(Exception ex)
        {
            _actualException = ex;
        }

        // for internal use
        public Exception Exception
        {
            get
            {
                return _actualException;
            }
        }

        [ScriptObjectConstructor("Exception", "An exception indicates that a problem occurred.")]
        public ScriptException(ScriptString message)
        {
            _actualException = new Exception((string)message);
        }

        [ScriptObjectProperty("Message", "The exception message.")]
        public ScriptString Message
        {
            get
            {
                return new ScriptString(_actualException.Message);
            }
        }

        #region ISerializable Members
        protected ScriptException(SerializationInfo info, StreamingContext context)
        {
            if (info == null) throw new System.ArgumentNullException("info");
            _actualException = (Exception)info.GetValue("e", typeof(Exception));
        }

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("e", _actualException, typeof(Exception));
        }
        #endregion
    }

    [Serializable]
    [DebuggerDisplay("ScriptString = {_string}")]
    [ScriptObjectClass("String", "Represents text; that is, a series of characters.")]
    public class ScriptString : ScriptObject, ISerializable
    {
        private string _string;

        [ScriptObjectConstructor("String", "Initializes a new instance of the String class.")]
        public ScriptString()
        {
            _string = "";
        }
        public ScriptString(string value)
        {
            _string = value;
        }
        public ScriptString(char value)
        {
            _string = value.ToString();
        }
        [ScriptObjectConstructor("String", "Initializes a new instance of the String class to the string representation of the specified value.")]
        [ScriptObjectConstructorParameter("Value", "The value to convert to a string.")]
        public ScriptString(IScriptObject value)
        {
            _string = value.ToPrimitiveObject().ToString();
        }

        public override string ToString()  // so we can see the value in the debugger
        {
            return "\"" + _string + "\"";
        }

        [ScriptObjectPropertyAttribute("Length", "Gets the number of characters in the string.")]
        public ScriptNumber Length
        {
            get { return new ScriptNumber(_string.Length); }
        }

        [ScriptObjectMethodAttribute("Contains", "Determines whether the this instance contains the specified String.")]
        [ScriptObjectMethodParameter("sought", "The string to seek.")]
        public ScriptBoolean Contains(ScriptString sought)  // all numbers must be doubles
        {
            return new ScriptBoolean(_string.Contains(sought._string));
        }
        [ScriptObjectMethodAttribute("Ends With", "Determines whether the end of this instance matches the specified String.")]
        [ScriptObjectMethodParameter("sought", "The string to seek.")]
        public ScriptBoolean EndsWith(ScriptString sought)  // all numbers must be doubles
        {
            return new ScriptBoolean(_string.EndsWith(sought._string));
        }
        [ScriptObjectMethodAttribute("Index Of", "Reports the index of the first occurrence of the specified String in this instance.")]
        [ScriptObjectMethodParameter("sought", "The string to seek.")]
        public ScriptNumber IndexOf(ScriptString sought)  // all numbers must be doubles
        {
            return new ScriptNumber(_string.IndexOf(sought._string));
        }
        [ScriptObjectMethodAttribute("Index Of", "Reports the index of the first occurrence of the specified String in this instance. The search starts at a specified character position.")]
        [ScriptObjectMethodParameter("sought", "The string to seek.")]
        [ScriptObjectMethodParameter("startIndex", "The search starting position.")]
        public ScriptNumber IndexOf(ScriptString sought, ScriptNumber startIndex)  // all numbers must be doubles
        {
            return new ScriptNumber(_string.IndexOf(sought._string, (int)startIndex));
        }
        [ScriptObjectMethodAttribute("Index Of", "Reports the index of the first occurrence of the specified String in this instance. The search starts at a specified character position and examines a specified number of character positions.")]
        [ScriptObjectMethodParameter("sought", "The string to seek.")]
        [ScriptObjectMethodParameter("startIndex", "The search starting position.")]
        [ScriptObjectMethodParameter("count", "The number of character positions to examine.")]
        public ScriptNumber IndexOf(ScriptString sought, ScriptNumber startIndex, ScriptNumber count)  // all numbers must be doubles
        {
            return new ScriptNumber(_string.IndexOf(sought._string, (int)startIndex, (int)count));
        }
        [ScriptObjectMethodAttribute("Insert", "Inserts a specified instance of String at a specified index position in this instance.")]
        [ScriptObjectMethodParameter("startIndex", "The index position of the insertion.")]
        [ScriptObjectMethodParameter("value", "The String to insert.")]
        public ScriptString Insert(ScriptNumber startIndex, ScriptString value)  // all numbers must be doubles
        {
            return new ScriptString(_string.Insert((int)startIndex, value._string));
        }
        [ScriptObjectMethodAttribute("To Lowercase", "Returns a copy of this instance in lowercase.")]
        public ScriptString ToLower()  // all numbers must be doubles
        {
            return new ScriptString(_string.ToLower());
        }
        [ScriptObjectMethodAttribute("To Uppercase", "Returns a copy of this instance in uppercase.")]
        public ScriptString ToUpper()  // all numbers must be doubles
        {
            return new ScriptString(_string.ToUpper());
        }
        [ScriptObjectMethodAttribute("Remove", "Deletes all the characters from this string beginning at a specified position and continuing through the last position.")]
        [ScriptObjectMethodParameter("startIndex", "The zero-based position to begin deleting characters.")]
        public ScriptString Remove(ScriptNumber startIndex)  // all numbers must be doubles
        {
            return new ScriptString(_string.Remove((int)startIndex));
        }
        [ScriptObjectMethodAttribute("Remove", "Deletes a specified number of characters from this instance beginning at a specified position.")]
        [ScriptObjectMethodParameter("startIndex", "The position in this instance to begin deleting characters.")]
        [ScriptObjectMethodParameter("count", "The number of characters to delete.")]
        public ScriptString Remove(ScriptNumber startIndex, ScriptNumber count)  // all numbers must be doubles
        {
            return new ScriptString(_string.Remove((int)startIndex, (int)count));
        }
        [ScriptObjectMethodAttribute("Replace", "Replaces all occurrences of a specified String in this instance with another specified String.")]
        [ScriptObjectMethodParameter("oldValue", "A String to be replaced.")]
        [ScriptObjectMethodParameter("newValue", "A String to replace all occurrences of oldValue.")]
        public ScriptString Replace(ScriptString oldValue, ScriptString newValue)  // all numbers must be doubles
        {
            return new ScriptString(_string.Replace(oldValue._string, newValue._string));
        }
        [ScriptObjectMethodAttribute("Starts With", "Determines whether the beginning of this instance matches the specified String.")]
        [ScriptObjectMethodParameter("sought", "The String to seek.")]
        public ScriptBoolean StartsWith(ScriptString sought)  // all numbers must be doubles
        {
            return new ScriptBoolean(_string.StartsWith(sought._string));
        }
        [ScriptObjectMethodAttribute("Substring", "Retrieves a substring from this instance. The substring starts at a specified character position.")]
        [ScriptObjectMethodParameter("startIndex", "The starting character position of a substring in this instance.")]
        public ScriptString Substring(ScriptNumber startIndex)  // all numbers must be doubles
        {
            return new ScriptString(_string.Substring((int)startIndex));
        }
        [ScriptObjectMethodAttribute("Substring", "Retrieves a substring from this instance. The substring starts at a specified character position and has a specified length.")]
        [ScriptObjectMethodParameter("startIndex", "The starting character position of a substring in this instance.")]
        [ScriptObjectMethodParameter("length", "The number of characters in the substring.")]
        public ScriptString Substring(ScriptNumber startIndex, ScriptNumber length)  // all numbers must be doubles
        {
            return new ScriptString(_string.Substring((int)startIndex, (int)length));
        }
        [ScriptObjectMethodAttribute("Trim", "Removes all occurrences of white space characters from the beginning and end of this instance.")]
        public ScriptString Trim()  // all numbers must be doubles
        {
            return new ScriptString(_string.Trim());
        }

        [ScriptObjectMethodAttribute("Split", "Returns an array of string that contain the substrings in this string that are delimited by a specified string.")]
        [ScriptObjectMethodParameter("Separator", "A string that delimits the substrings in this string.")]
        public IScriptArray Split(ScriptString separator)
        {
            return Split(separator, new ScriptNumber(int.MaxValue));
        }

        [ScriptObjectMethodAttribute("Split", "Returns an array of string that contain the substrings in this string that are delimited by a specified string.")]
        [ScriptObjectMethodParameter("Separator", "A string that delimits the substrings in this string.")]
        [ScriptObjectMethodParameter("Count", "The maximum number of substrings to return.")]
        public IScriptArray Split(ScriptString separator, ScriptNumber count)
        {
            // Split the string.
            string[] a;
            a = _string.Split(new string[] { (string)separator }, (int)count, StringSplitOptions.None);

            // Convert the array to a ScriptArray.
            ScriptArrayMarshalByValue result = new ScriptArrayMarshalByValue(true, true);
            for (int i = 0; i < a.Length; i++)
                result.SetItemInternal(i, new ScriptString(a[i]));

            return result;
        }

        private string[] compactFrameworkSplit(string s, string delimeter, int count) // compact framework support for string delimiters.
        {
            if (s == null)
                throw new ArgumentNullException("stringToBeSplit is null.");
            if (delimeter == null)
                throw new ArgumentNullException("delimeter is null.");

            int ct = 0; // counter


            int dsum = 0;
            int ssum = 0;
            int dl = delimeter.Length;
            int sl = s.Length;

            if (dl == 0 || sl == 0 || sl < dl)
                return new string[] { s };

            char[] cd = delimeter.ToCharArray();
            char[] cs = s.ToCharArray();
            List<string> retlist = new List<string>();

            for (int i = 0; i < dl; i++)
            {
                dsum += cd[i];
                ssum += cs[i];
            }

            int start = 0;
            for (int i = start; i < sl - dl; i++)
            {
                if (i >= start && dsum == ssum && s.Substring(i, dl) == delimeter)
                {
                    retlist.Add(s.Substring(start, i - start));
                    start = i + dl;
                    ct++;
                }

                ssum += cs[i + dl] - cs[i];

                // we found the max # of elements
                if (ct == count)
                    break;
            }

            if (ct != count)
            {
                if (dsum == ssum && s.Substring(sl - dl, dl) == delimeter)
                {
                    retlist.Add(s.Substring(start, sl - dl - start));
                    retlist.Add("");
                }
                else
                {
                    retlist.Add(s.Substring(start, sl - start));
                }
            }

            return retlist.ToArray();
        }


        [ScriptObjectMethodAttribute("Join", "Concatenates a specified string between each element of the specified array, yeilding a single concatenated string.")]
        [ScriptObjectMethodParameter("Separator", "A string.")]
        [ScriptObjectMethodParameter("Value", "An array of objects that will be converted to strings.")]
        [ScriptObjectMethodParameter("startIndex", "The first array element in value to use.")]
        [ScriptObjectMethodParameter("Count", "The number of elements of value use.")]
        public static ScriptString Join(ScriptString separator, ScriptArrayMarshalByReference value)
        {
            return Join(separator, value, new ScriptNumber(0), value.Length);
        }

        [ScriptObjectMethodAttribute("Join", "Concatenates a specified string between each element of the specified array, yeilding a single concatenated string.")]
        [ScriptObjectMethodParameter("Separator", "A string.")]
        [ScriptObjectMethodParameter("Value", "An array of objects that will be converted to strings.")]
        [ScriptObjectMethodParameter("startIndex", "The first array element in value to use.")]
        [ScriptObjectMethodParameter("Count", "The number of elements of value use.")]
        public static ScriptString Join(ScriptString separator, ScriptArrayMarshalByReference value, ScriptNumber startIndex, ScriptNumber count)
        {
            string[] a = new string[value.PrimitiveBoundsLength];
            for (int i = 0; i < value.PrimitiveBoundsLength; i++)
            {
                // allow null elements (System.String.Join() converts then to empty strings.)
                IScriptObject element = value[i + value.PrimitiveLowestIndex];
                if (element != null)
                    a[i] = element.ToPrimitiveString();
            }

            string joined = string.Join((string)separator, a, (int)startIndex, (int)count);

            return new ScriptString(joined);
        }



        [ScriptObjectMethodAttribute("Remove End", "Removes the specified number of characters from the end of this instance.")]
        [ScriptObjectMethodParameter("startIndex", "The position in this instance to begin deleting characters.")]
        [ScriptObjectMethodParameter("count", "The number of characters to delete.")]
        public ScriptString RemoveEnd(ScriptNumber count)
        {
            return new ScriptString(cutEnd(_string, (int)count));
        }
        [ScriptObjectMethodAttribute("Remove Start", "Removes the specified number of characters from the start of this instance.")]
        [ScriptObjectMethodParameter("count", "The number of characters to remove from the start of this instance.")]
        public ScriptString RemoveStart(ScriptNumber count)
        {
            int length = (int)count;

            if (_string.Length <= length) return new ScriptString("");

            return new ScriptString(_string.Substring(length));
        }
        [ScriptObjectMethodAttribute("Start", "Retrieve the specified number of characters from the start of this instance.")]
        [ScriptObjectMethodParameter("count", "The number of characters to retrieve from the start of this instance.")]
        public ScriptString Start(ScriptNumber count)
        {
            int length = (int)count;

            if (_string.Length <= length) return new ScriptString(_string);
            return new ScriptString(_string.Substring(0, length));
        }
        [ScriptObjectMethodAttribute("End", "Retrieve the specified number of characters from the end of this instance.")]
        [ScriptObjectMethodParameter("count", "The number of characters to retrieve from the end of this instance.")]
        public ScriptString End(ScriptNumber count)
        {
            int length = (int)count;

            if (_string.Length <= length) return new ScriptString(_string);

            return new ScriptString(_string.Substring(_string.Length - length));
        }
        [ScriptObjectMethodAttribute("To Title Case", "Returns a copy of this String in titlecase. Generally, title casing converts the first character of a word to uppercase and the rest of the characters to lowercase. However, a word that is entirely uppercase, such as an acronym, is not converted.")]
        public ScriptString ToTitleCase()
        {
            return new ScriptString(System.Threading.Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(_string));
        }
        [ScriptObjectMethodAttribute("To Friendly Name", "Returns the friendly name of this String. The friendly name of a string adds a space before each upper cased letter in the string where a space did not previously exist.")]
        public ScriptString ToFriendlyName()
        {
            return new ScriptString(toFriendlyName(_string, false));
        }


        public override string ToPrimitiveString()
        {
            return _string;
        }

        public override double ToPrimitiveDouble()
        {
            return Convert.ToDouble(_string);
        }

        public override Int32 ToPrimitiveInt32()
        {
            return Convert.ToInt32(_string);
        }

        public override bool ToPrimitiveBoolean()
        {
            return Convert.ToBoolean(_string);
        }

        public override object ToPrimitiveObject()
        {
            return (object)_string;
        }

        public static explicit operator byte(ScriptString value)
        {
            return Convert.ToByte(value._string);
        }

        public static explicit operator sbyte(ScriptString value)
        {
            return Convert.ToSByte(value._string);
        }

        public static explicit operator short(ScriptString value)
        {
            return Convert.ToInt16(value._string);
        }

        public static explicit operator ushort(ScriptString value)
        {
            return Convert.ToUInt16(value._string);
        }

        public static explicit operator int(ScriptString value)
        {
            return Convert.ToInt32(value._string);
        }

        public static explicit operator uint(ScriptString value)
        {
            return Convert.ToUInt32(value._string);
        }

        public static explicit operator long(ScriptString value)
        {
            return Convert.ToInt64(value._string);
        }

        public static explicit operator ulong(ScriptString value)
        {
            return Convert.ToUInt64(value._string);
        }

        public static explicit operator decimal(ScriptString value)
        {
            return Convert.ToDecimal(value._string);
        }

        public static explicit operator double(ScriptString value)
        {
            return Convert.ToDouble(value._string);
        }

        public static explicit operator float(ScriptString value)
        {
            return Convert.ToSingle(value._string);
        }

        //public static explicit operator string(ScriptString value)
        //{
        //    return (string)value._string;
        //}

        public static implicit operator string(ScriptString value)
        {
            if (value == null) // return null so that the ScriptString can be used in string concatenation even when it is null.
                return null;
            else
                return value._string;
        }

        public static explicit operator bool(ScriptString value)
        {
            return Convert.ToBoolean(value._string);
        }


        public static ScriptBoolean operator ==(ScriptString s1, ScriptString s2)
        {
            string str1 = null;
            string str2 = null;

            // Box as object so we can test for null equality without reentering this method causing a resursion stack overflow. I used to use a try/catch to get around that issue but this is cleaner.
            object o1 = s1;
            object o2 = s2;

            if (o1 != null)
                str1 = s1._string;

            if (o2 != null)
                str2 = s2._string;
            
            return new ScriptBoolean(str1 == str2);
        }

        public static ScriptBoolean operator !=(ScriptString s1, ScriptString s2)
        {
            string str1 = null;
            string str2 = null;

            // Box as object so we can test for null equality without reentering this method causing a resursion stack overflow. I used to use a try/catch to get around that issue but this is cleaner.
            object o1 = s1;
            object o2 = s2;

            if (o1 != null)
                str1 = s1._string;

            if (o2 != null)
                str2 = s2._string;

            return new ScriptBoolean(str1 != str2);
        }

        public static ScriptBoolean operator <(ScriptString s1, ScriptString s2)
        {
            if (s1 == null)
                throw new ArgumentNullException("s1", "Operator '<' cannot be applied to operands of type '<null>' and 'String'.");
            if (s2 == null)
                throw new ArgumentNullException("s2", "Operator '<' cannot be applied to operands of type 'String' and '<null>'.");

            return new ScriptBoolean( string.Compare(s1, s2) < 0);
        }

        public static ScriptBoolean operator >(ScriptString s1, ScriptString s2)
        {
            if (s1 == null)
                throw new ArgumentNullException("s1", "Operator '>' cannot be applied to operands of type '<null>' and 'String'.");
            if (s2 == null)
                throw new ArgumentNullException("s2", "Operator '>' cannot be applied to operands of type 'String' and '<null>'.");

            return new ScriptBoolean(string.Compare(s1, s2) > 0);
        }

        public static ScriptBoolean operator <=(ScriptString s1, ScriptString s2)
        {
            if (s1 == null)
                throw new ArgumentNullException("s1", "Operator '<=' cannot be applied to operands of type '<null>' and 'String'.");
            if (s2 == null)
                throw new ArgumentNullException("s2", "Operator '<=' cannot be applied to operands of type 'String' and '<null>'.");

            return new ScriptBoolean(string.Compare(s1, s2) <= 0);
        }

        public static ScriptBoolean operator >=(ScriptString s1, ScriptString s2)
        {
            if (s1 == null)
                throw new ArgumentNullException("s1", "Operator '>=' cannot be applied to operands of type '<null>' and 'String'.");
            if (s2 == null)
                throw new ArgumentNullException("s2", "Operator '>=' cannot be applied to operands of type 'String' and '<null>'.");

            return new ScriptBoolean(string.Compare(s1, s2) >= 0);
        }

        public static ScriptString operator +(ScriptString s1, ScriptString s2)
        {
            return new ScriptString((string)s1._string + (string)s2._string);
        }


        /// <summary>
        /// returns a friendly name of a string
        /// eg. "SomeUtilsText" becomes "Some Utils Text"
        /// eg2. "BillInvoiceID" becomes "Bill Invoice" if trimIDText = true
        /// 
        /// "UseSSL" becomes "Use SSL"
        /// "UseSslAndSmtp" becomes "Use Ssl And Smtp"
        /// "UseSslAndSMTP" becomes "Use Ssl And SMTP"
        /// "UseSSLAndSmtp" becomes "Use SSL And Smtp"
        ///
        /// </summary>
        /// <param name="input">the input string</param>
        /// <param name="trimIDText">if "ID" text should be cut off the end of the string</param>
        /// <returns>a friendly name string</returns>
        private string toFriendlyName(string s, bool trimIDText)
        {
            if (string.IsNullOrEmpty(s)) return s;
            s = s.Trim(); //trim it
            if (s.ToUpper() == s) return s;    //if its all capitals we cant do anything with it
            StringBuilder sb = new StringBuilder(s.Length);
            char? last = null;
            for (int i = 0; i < _string.Length; i++)
            //foreach (char c in input.ToCharArray())
            {
                char c = s[i];
                char? next = (i < _string.Length - 1) ? (s[i + 1]) : (char?)null;

                //if (last != null && Char.IsUpper(c)) // && Char.IsLower(last ?? char.MinValue))
                if (
                    (last != null && Char.IsUpper(c) &&
                        ((Char.IsLower((char)last)) ||
                        (last != null && next != null && Char.IsLower((char)next)))))
                    sb.Append(" ").Append(c);
                else
                    sb.Append(c);
                last = c;
            }
            if (trimIDText)
            {
                //if the string ends with ' id' cut it off
                string strOutput = sb.ToString();
                if (strOutput.ToLower().EndsWith(" id"))
                    return cutEnd(strOutput, 3);
            }
            return sb.ToString();
        }

        private string cutEnd(string s, int length)
        {
            if (s.Length <= length) return string.Empty;
            return s.Substring(0, s.Length - length);
        }

        public override object Clone()
        {
            // This provides better performance than the base Clone().
            return new ScriptString(_string);
        }

        #region ISerializable Members
        protected ScriptString(SerializationInfo info, StreamingContext context)
        {
            if (info == null) throw new System.ArgumentNullException("info");
            _string = info.GetString("s");
        }

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("s", _string);
        }
        #endregion
    }

    public abstract class ScriptObject : IScriptObject, ISerializable, ISerialize, ICloneable
    {

        #region IScriptObject Members

        /// <summary>
        ///  Clone the object by serializing it then deserializing. For better performance each IScriptObject type should override this and provide a quicker routine.
        /// </summary>
        /// <returns>A new object that is a copy of this instance.</returns>
        /// <exception cref="System.NotSupportedException">The type does not support cloning via serialization/deserialization.</exception>
        public virtual object Clone()
        {
            return ScriptObject.Clone(this);
        }

        public static object Clone(IScriptObject o)
        {
            try
            {
                byte[] b = o.Serialize();
                return ScriptObject.Deserialize(b);
            }
            catch (Exception ex)
            {
                // it doesn't really support serialization so we'll just use the remote proxy.
                throw new NotSupportedException("The '" + o.GetType().Name + "' type does not support cloning via serialization/deserialization.", ex);
            }
        }

        /// <summary>
        /// Clones a IScriptObject and returns a local copy (as opposed to a remote transparent proxy).
        /// </summary>
        /// <param name="o"></param>
        /// <returns></returns>
        public static IScriptObject CloneLocally(IScriptObject o)
        {
            // Use casting for better performance than the base Clone().

            if (o is ScriptString)
                return new ScriptString((string)(ScriptString)o);
            else if (o is ScriptNumber)
                return new ScriptNumber((double)(ScriptNumber)o);
            else if (o is ScriptBoolean)
                return new ScriptBoolean((bool)(ScriptBoolean)o);
            else if (o is ScriptDateTime)
                return new ScriptDateTime((DateTime)(ScriptDateTime)o);
            else if (o is ScriptTimeSpan)
                return new ScriptTimeSpan((TimeSpan)(ScriptTimeSpan)o);
            else if (o is ScriptByteArray)
            {
                byte[] b1 = (byte[])(ScriptByteArray)o;
                byte[] b2 = new byte[b1.Length];
                System.Buffer.BlockCopy(b1, 0, b2, 0, b1.Length);

                return new ScriptByteArray(b2);
            }
            else
            {
                byte[] b = ((ISerialize)o).Serialize();
                return Deserialize(b);
            }
        }


        public virtual ScriptObjectInvokeResult GetIndexer(int index, out IScriptObject result)
        {
            return ScriptObject.GetIndexer(this, index, out result);
        }

        public static ScriptObjectInvokeResult GetIndexer(IScriptObject o, int index, out IScriptObject result)
        {
            string defaultMemberName = getDefaultMemberName(o.GetType());
            if (defaultMemberName == null)
                throw new Exception("The attempt to retrieve index " + index + " from type " + o.GetType().Name + " failed because the type does not contain an indexer.");

            return GetProperty(o, defaultMemberName, new object[] { index }, out result);
        }


        public virtual ScriptObjectInvokeResult GetProperty(string propertyName, out IScriptObject result)
        {
            return ScriptObject.GetProperty(this, propertyName, null, out result);
        }
        public static ScriptObjectInvokeResult GetProperty(IScriptObject o, string propertyName, out IScriptObject result)
        {
            return ScriptObject.GetProperty(o, propertyName, null, out result);
        }

        public virtual ScriptObjectInvokeResult GetProperty(string propertyName, object[] index, out IScriptObject result)
        {
            return GetProperty(this, propertyName, index, out result);
        }
        public static ScriptObjectInvokeResult GetProperty(IScriptObject o, string propertyName, object[] index, out IScriptObject result)
        {
            result = null;

            // Loop through the object inheritence hierarchy searching for the property.  Without the looping we can get ambiguous name errors.
            PropertyInfo pi = null;
            for (Type type = o.GetType(); pi == null && type != typeof(MarshalByRefObject); type = type.BaseType)
            {
                // BindingFlags.DeclaredOnly specifies that only members declared at the level of the supplied type's hierarchy should be considered. Inherited members are not considered.
                pi = type.GetProperty(propertyName, BindingFlags.DeclaredOnly | BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
            }
            if (pi == null)
                return ScriptObjectInvokeResult.UndefinedMember;


            if (pi.CanRead == false)
                return ScriptObjectInvokeResult.IsWriteOnly;

            if (!typeof(IScriptObject).IsAssignableFrom(pi.PropertyType))
                return ScriptObjectInvokeResult.InvalidType;

            // Check if the property has the [ScriptObjectPropertyAttribute] flag.
            if (pi.GetCustomAttributes(typeof(ScriptObjectPropertyAttribute), false).Length == 0)
                return ScriptObjectInvokeResult.MissingScriptPropertyAttribute;

            result = (IScriptObject)pi.GetValue(o, index);

            return ScriptObjectInvokeResult.Success;
        }


        public virtual ScriptObjectInvokeResult InvokeMethod(string methodName, IScriptObject[] parameters, out IScriptObject result)
        {
            return InvokeMethod(this, methodName, parameters, out result);
        }

        public static ScriptObjectInvokeResult InvokeMethod(IScriptObject o, string methodName, object[] parameters, out IScriptObject result)
        {
            result = null;

            Type type = o.GetType();

            //MethodInfo mi = type.GetMethod(methodName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance, null, parameterTypes, null);
            MethodInfo mi = GetMethodInfo(type, methodName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance, parameters);
            if (mi == null)
            {
                // Find all members with the method name
                MemberInfo[] memis = type.GetMember(methodName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);

                // If we found at least one method member then the arguments must not have matched above.
                foreach (MemberInfo memi in memis)
                    if (memi.MemberType == MemberTypes.Method)
                        return ScriptObjectInvokeResult.WrongNumberOfArguments; // this can also mean that the arguments don't match

                return ScriptObjectInvokeResult.UndefinedMember;
            }

            // Check if the method has the [ScriptObjectMethodAttribute] flag.
            if (mi.GetCustomAttributes(typeof(ScriptObjectMethodAttribute), false).Length == 0)
                return ScriptObjectInvokeResult.MissingScriptMethodAttribute;

            // Methods can return void or Token
            if (mi.ReturnType == typeof(void))
                mi.Invoke(o, parameters);
            else if (typeof(IScriptObject).IsAssignableFrom(mi.ReturnType))
                result = (IScriptObject)mi.Invoke(o, parameters);
            else
                return ScriptObjectInvokeResult.InvalidType;

            return ScriptObjectInvokeResult.Success;
        }

        /// <summary>
        /// Returns information on all exposed Script Member properties.
        /// </summary>
        public virtual List<ScriptObjectConstructorInfo> ScriptObjectConstructors
        {
            get
            {
                Type type = this.GetType();

                return GetScriptObjectConstructors(type);
            }
        }
        public static List<ScriptObjectConstructorInfo> GetScriptObjectConstructors(Type type)
        {
            return GetScriptObjectConstructors(type, true);
        }
        public static List<ScriptObjectConstructorInfo> GetScriptObjectConstructors(Type type, bool enforceScriptObjectType)
        {
            if (enforceScriptObjectType)
                if (!typeof(IScriptObject).IsAssignableFrom(type))
                    throw new ArgumentException("The type must implement IScriptObject.", "type");

            List<ScriptObjectConstructorInfo> list = new List<ScriptObjectConstructorInfo>();

            ConstructorInfo[] ci = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance);

            for (int i = 0; i < ci.Length; i++)
            {
                ScriptObjectConstructorAttribute[] constructorAttributes = (ScriptObjectConstructorAttribute[])ci[i].GetCustomAttributes(typeof(ScriptObjectConstructorAttribute), false);

                // Check if the constructor has the [ScriptObjectConstructorAttribute] attribute.
                if (constructorAttributes.Length > 0)
                {
                    ScriptObjectConstructorInfo constr = new ScriptObjectConstructorInfo();
                    constr.Attribute = constructorAttributes[0];

                    ScriptObjectConstructorParameterAttribute[] parameterAttributes = (ScriptObjectConstructorParameterAttribute[])ci[i].GetCustomAttributes(typeof(ScriptObjectConstructorParameterAttribute), false);

                    // Put together the list of parameters and parameter types (in order).
                    ParameterInfo[] pis = ci[i].GetParameters();
                    constr.ParameterAttributes = new ScriptObjectConstructorParameterAttribute[pis.Length];
                    constr.ParameterTypes = new Type[pis.Length];
                    foreach (ParameterInfo pi in pis)
                    {
                        int position = pi.Position; // testing

                        // Ensure the parameter is a sub-class of IScriptObject
                        if (!typeof(IScriptObject).IsAssignableFrom(pi.ParameterType))
                            throw new Exception("The constructor '" + ci[i].Name + "' contains the parameter '" + pi.Name + "' that does not implement the IScriptObject type.");

                        // Find matching [ScriptObjectMethodParameterAttribute] attribute
                        foreach (ScriptObjectConstructorParameterAttribute pa in parameterAttributes)
                        {
                            // If found then add it to the array so the array is in the proper order.
                            if (pa.ParameterName.Equals(pi.Name, StringComparison.OrdinalIgnoreCase))
                            {
                                constr.ParameterAttributes[position] = pa;
                                break;
                            }
                        }

                        // Set parameter type
                        constr.ParameterTypes[position] = pi.ParameterType;

                        // Make sure we found the matching parameter attribute
                        if (constr.ParameterAttributes[position] == null)
                            constr.ParameterAttributes[position] = new ScriptObjectConstructorParameterAttribute(pi.Name, "Error: The constructor '" + ci[i].Name + "' is missing a ScriptObjectMethodParameterAttribute for '" + pi.Name + "'.");
                    }

                    list.Add(constr);
                }
            }

            return list;
        }

        public string ScriptObjectDescription
        {
            get
            {
                Type type = this.GetType();

                return GetScriptObjectDescription(type);
            }
        }

        public static string GetScriptObjectDescription(Type type)
        {
            return GetScriptObjectDescription(type, true);
        }
        public static string GetScriptObjectDescription(Type type, bool enforceScriptObjectType)
        {
            if (enforceScriptObjectType)
                if (!typeof(IScriptObject).IsAssignableFrom(type))
                    throw new ArgumentException("The type must implement IScriptObject.", "type");

            ScriptObjectClassAttribute[] attributes = (ScriptObjectClassAttribute[])type.GetCustomAttributes(typeof(ScriptObjectClassAttribute), false);

            // Check if the class has the [ScriptObjectClassAttribute] attribute.
            if (attributes.Length > 0)
                return attributes[0].Description;
            else
                return "";
        }




        public string ScriptObjectDisplayName
        {
            get
            {
                Type type = this.GetType();

                return GetScriptObjectDisplayName(type);
            }
        }

        public static string GetScriptObjectDisplayName(Type type)
        {
            return GetScriptObjectDisplayName(type, true);
        }
        public static string GetScriptObjectDisplayName(Type type, bool enforceScriptObjectType)
        {
            if (enforceScriptObjectType)
                if (!typeof(IScriptObject).IsAssignableFrom(type))
                    throw new ArgumentException("The type must implement IScriptObject.", "type");

            ScriptObjectClassAttribute[] attributes = (ScriptObjectClassAttribute[])type.GetCustomAttributes(typeof(ScriptObjectClassAttribute), false);

            // Check if the class has the [ScriptObjectClassAttribute] attribute.
            if (attributes.Length > 0)
                return attributes[0].DisplayName;
            else
                return "";
        }


        /// <summary>
        /// Returns information on all exposed Script Member properties.
        /// </summary>
        public virtual List<ScriptObjectMethodInfo> ScriptObjectMethods
        {
            get
            {
                Type type = this.GetType();

                return GetScriptObjectMethods(type);
            }
        }
        public static List<ScriptObjectMethodInfo> GetScriptObjectMethods(Type type)
        {
            return GetScriptObjectMethods(type, true);
        }

        public static List<ScriptObjectMethodInfo> GetScriptObjectMethods(Type type, bool enforceScriptObjectType)
        {
            if (enforceScriptObjectType)
                if (!typeof(IScriptObject).IsAssignableFrom(type))
                    throw new ArgumentException("The type must implement IScriptObject.", "type");

            List<ScriptObjectMethodInfo> list = new List<ScriptObjectMethodInfo>();

            MethodInfo[] mi = type.GetMethods(BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);

            for (int i = 0; i < mi.Length; i++)
            {
                if (typeof(IScriptObject).IsAssignableFrom(mi[i].ReturnType) || mi[i].ReturnType == typeof(void))
                {
                    ScriptObjectMethodAttribute[] scriptMemberAttributes = (ScriptObjectMethodAttribute[])mi[i].GetCustomAttributes(typeof(ScriptObjectMethodAttribute), false);

                    ObsoleteAttribute[] obsoleteAttributes = (ObsoleteAttribute[])mi[i].GetCustomAttributes(typeof(ObsoleteAttribute), false);

                    // Check if the method has the [ScriptObjectMethodAttribute] attribute.
                    if (scriptMemberAttributes.Length > 0)
                    {
                        ScriptObjectMethodInfo method = new ScriptObjectMethodInfo();
                        method.MethodName = mi[i].Name;
                        method.Attribute = scriptMemberAttributes[0];
                        method.AssemblyQualifiedName = mi[i].ReturnType.AssemblyQualifiedName;
                        method.FullReturnTypeName = mi[i].ReturnType.FullName;
                        method.IsStatic = mi[i].IsStatic;
                        method.IsObsolete = obsoleteAttributes.Length > 0;

                        ScriptObjectMethodParameterAttribute[] parameterAttributes = (ScriptObjectMethodParameterAttribute[])mi[i].GetCustomAttributes(typeof(ScriptObjectMethodParameterAttribute), false);

                        // Put together the list of parameters and parameter types (in order).
                        ParameterInfo[] pis = mi[i].GetParameters();
                        method.ParameterAttributes = new ScriptObjectMethodParameterAttribute[pis.Length];
                        method.ParameterTypes = new Type[pis.Length];
                        foreach (ParameterInfo pi in pis)
                        {
                            int position = pi.Position; // testing

                            // Ensure the parameter is a sub-class of IScriptObject
                            if (!typeof(IScriptObject).IsAssignableFrom(pi.ParameterType))
                                throw new Exception("The method '" + mi[i].Name + "' contains the parameter '" + pi.Name + "' that does not implement the IScriptObject type.");

                            // Find matching [ScriptObjectMethodParameterAttribute] attribute
                            foreach (ScriptObjectMethodParameterAttribute pa in parameterAttributes)
                            {
                                // If found then add it to the array so the array is in the proper order.
                                if (pa.ParameterName.Equals(pi.Name, StringComparison.OrdinalIgnoreCase))
                                {
                                    method.ParameterAttributes[position] = pa;
                                    break;
                                }
                            }

                            // Set parameter type
                            method.ParameterTypes[position] = pi.ParameterType;

                            // Make sure we found the matching parameter attribute
                            if (method.ParameterAttributes[position] == null)
                                method.ParameterAttributes[position] = new ScriptObjectMethodParameterAttribute(pi.Name, "Error: The method '" + mi[i].Name + "' is missing a ScriptObjectMethodParameterAttribute for '" + pi.Name + "'.");
                        }


                        // Since we can't set the type to something that the caller doesn't have access to, determine the base class that exists within this assembly.
                        if (mi[i].ReturnType == typeof(void))
                            method.KnownReturnType = mi[i].ReturnType;
                        else
                        {
                            string baseNamespace = typeof(IScriptObject).Namespace;
                            for (Type type1 = mi[i].ReturnType; type1 != null; type1 = type1.BaseType)
                            {
                                if (type1.Namespace == baseNamespace)
                                {
                                    method.KnownReturnType = type1;
                                    break;
                                }
                            }
                        }


                        list.Add(method);
                    }
                }
            }

            return list;
        }

        /// <summary>
        /// Returns information on all exposed Script Member properties.
        /// </summary>
        public virtual Dictionary<string, ScriptObjectPropertyInfo> ScriptObjectProperties
        {
            get
            {
                Type type = this.GetType();

                return GetScriptObjectProperties(type);
            }
        }
        public static Dictionary<string, ScriptObjectPropertyInfo> GetScriptObjectProperties(Type type)
        {
            return GetScriptObjectProperties(type, true);
        }

        public static Dictionary<string, ScriptObjectPropertyInfo> GetScriptObjectProperties(Type type, bool enforceScriptObjectType)
        {
            if (enforceScriptObjectType)
                if (!typeof(IScriptObject).IsAssignableFrom(type))
                    throw new ArgumentException("The type must implement IScriptObject.", "t");

            Dictionary<string, ScriptObjectPropertyInfo> dict = new Dictionary<string, ScriptObjectPropertyInfo>(StringComparer.OrdinalIgnoreCase);

            PropertyInfo[] pi = type.GetProperties(BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);

            for (int i = 0; i < pi.Length; i++)
            {
                if (typeof(IScriptObject).IsAssignableFrom(pi[i].PropertyType))
                {
                    ScriptObjectPropertyAttribute[] scriptMemberAttributes = (ScriptObjectPropertyAttribute[])pi[i].GetCustomAttributes(typeof(ScriptObjectPropertyAttribute), false);

                    ObsoleteAttribute[] obsoleteAttributes = (ObsoleteAttribute[])pi[i].GetCustomAttributes(typeof(ObsoleteAttribute), false);

                    // Check if the property has the [ScriptObjectPropertyAttribute] attribute.
                    if (scriptMemberAttributes.Length > 0)
                    {
                        ScriptObjectPropertyInfo property = new ScriptObjectPropertyInfo();
                        property.PropertyName = pi[i].Name;
                        property.CanRead = pi[i].CanRead;
                        property.CanWrite = pi[i].CanWrite;
                        property.Attribute = scriptMemberAttributes[0];
                        property.AssemblyQualifiedName = pi[i].PropertyType.AssemblyQualifiedName;
                        property.FullTypeName = pi[i].PropertyType.FullName;
                        if (pi[i].GetGetMethod(false) != null)
                            property.IsReadStatic = pi[i].GetGetMethod(false).IsStatic;
                        if (pi[i].GetSetMethod(false) != null)
                            property.IsWriteStatic = pi[i].GetSetMethod(false).IsStatic;
                        property.IsObsolete = obsoleteAttributes.Length > 0;
                        property.IsClassMember = true;

                        // Since we can't set the type to something that the caller doesn't have access to, determine the base class that exists within this assembly.
                        string baseNamespace = typeof(IScriptObject).Namespace;
                        for (Type type1 = pi[i].PropertyType; type1 != null; type1 = type1.BaseType)
                        {
                            if (type1.Namespace == baseNamespace)
                            {
                                property.KnownType = type1;
                                break;
                            }
                        }


                        dict.Add(pi[i].Name, property);
                    }
                }
            }

            return dict;
        }
        

        public virtual byte[] Serialize()
        {
            return ScriptObject.Serialize(this);
        }
        public static byte[] Serialize(IScriptObject o)
        {
            // Performance Tests (enumerating over a 256 element array):
            //   SLOW: Using normal remoting marshal by reference: 556-573 milliseconds
            //   FASTER: Using the ToPrimitiveArray() method and enumerating over the resulting object array: 11-17 milliseconds
            //   FASTEST: Using the Serialize/Deserialize() methods and enumerating over the resulting local ScriptArray: 4-8 milliseconds (but there were a couple rare instances where it took about 28 milliseconds).

            if (!o.GetType().IsSerializable)
                throw new ArgumentException("The type must be serializable.", "source");

            System.Runtime.Serialization.IFormatter formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
            using (MemoryStream stream = new MemoryStream())
            {
                formatter.Serialize(stream, o);
                return stream.ToArray();
            }
        }

        /// <summary>
        /// Deserializes the object from a byte array.
        /// </summary>
        public static IScriptObject Deserialize(byte[] serializedObjectData)
        {
            System.Runtime.Serialization.IFormatter formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
            using (MemoryStream ms = new MemoryStream(serializedObjectData))
            {
                object o = formatter.Deserialize(ms);
                return (IScriptObject)o;
            }
        }

        public virtual ScriptObjectInvokeResult SetIndexer(int index, IScriptObject value)
        {
            return SetIndexer(this, index, value);
        }
        public static ScriptObjectInvokeResult SetIndexer(IScriptObject o, int index, IScriptObject value)
        {
            string defaultMemberName = getDefaultMemberName(o.GetType());
            if (defaultMemberName == null)
                throw new Exception("The attempt to set index " + index + " in type " + o.GetType().Name + " failed because the type does not contain an indexer.");

            return SetProperty(o, defaultMemberName, value, new object[] { index });
        }

        private static string getDefaultMemberName(Type t)
        {
            // To determine whether a type has a default property, use the GetCustomAttributes(Type, Boolean) method to test for the DefaultMemberAttribute attribute. If the type has DefaultMemberAttribute, the MemberName property returns the name of the default property.

            DefaultMemberAttribute[] defaultMemberAttribute = (DefaultMemberAttribute[])t.GetCustomAttributes(typeof(DefaultMemberAttribute), false);
            if (defaultMemberAttribute.Length == 0)
                return null;

            // In C# this will always be "Item".
            return defaultMemberAttribute[0].MemberName;
        }


        public virtual ScriptObjectInvokeResult SetProperty(string propertyName, IScriptObject value)
        {
            return SetProperty(propertyName, value, null);
        }

        public virtual ScriptObjectInvokeResult SetProperty(string propertyName, IScriptObject value, object[] index)
        {
            return ScriptObject.SetProperty(this, propertyName, value, index);
        }
        public static ScriptObjectInvokeResult SetProperty(IScriptObject o, string propertyName, IScriptObject value, object[] index)
        {
            // Loop through the object inheritence hierarchy searching for the property.  Without the looping we can get ambiguous name errors.
            PropertyInfo pi = null;
            for (Type type = o.GetType(); pi == null && type != typeof(MarshalByRefObject); type = type.BaseType)
            {
                // BindingFlags.DeclaredOnly specifies that only members declared at the level of the supplied type's hierarchy should be considered. Inherited members are not considered.
                pi = type.GetProperty(propertyName, BindingFlags.DeclaredOnly | BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
            }
            if (pi == null)
                return ScriptObjectInvokeResult.UndefinedMember;


            if (pi.CanWrite == false)
                return ScriptObjectInvokeResult.IsReadOnly;

            if (!typeof(IScriptObject).IsAssignableFrom(pi.PropertyType))
                return ScriptObjectInvokeResult.InvalidType;

            // Check if the property has the [ScriptObjectPropertyAttribute] flag.
            if (pi.GetCustomAttributes(typeof(ScriptObjectPropertyAttribute), false).Length == 0)
                return ScriptObjectInvokeResult.MissingScriptPropertyAttribute;

            pi.SetValue(o, value, index);

            return ScriptObjectInvokeResult.Success;
        }

        public static MethodInfo GetMethodInfo(Type type, string methodName, BindingFlags bindingAttr, object[] argsArray)
        {
            // Are any of the arguments null?
            for (int i = 0; i < argsArray.Length; i++)
            {
                if (argsArray[i] == null)
                {
                    // At least one argument is null so we have to manually try to find the appropraite method.
                    MethodInfo matchedMethod = null;

                    // We can't use Type.GetTypeArray() or type.GetMethod() since in this case, so manually find the method.
                    MethodInfo[] methodInfos = type.GetMethods(bindingAttr);
                    foreach (MethodInfo mi in methodInfos)
                    {
                        // Compare the method names.
                        if (mi.Name.Equals(methodName, StringComparison.OrdinalIgnoreCase))
                        {
                            ParameterInfo[] pis = mi.GetParameters();

                            // Do the methods have the same # of parameters as our list?
                            if (pis.Length == argsArray.Length)
                            {
                                // Compare the parameter types.
                                for (int j = 0; j < argsArray.Length; j++)
                                {
                                    if (argsArray[j] != null && pis[j].ParameterType != argsArray[j].GetType())
                                    {
                                        // The parameters did not match.
                                        // Because we added IScriptObject after version 1.0 was released and changed some parameters from ScriptObject to IScriptObject we need to make a special exception for those cases...
                                        // This is only an issue for touch screens built before version 1.1. Once the action list is edited and saved then the method paramters will use the newer parameter type (usually IScriptObject).
                                        if ((pis[j].ParameterType == typeof(ScriptObject) || pis[j].ParameterType == typeof(IScriptObject)) && (argsArray[j].GetType() == typeof(ScriptObject) || argsArray[j].GetType() == typeof(IScriptObject)))
                                        {
                                            // Allow this exception since we are dealing with ScriptObject and/or IScriptObject.
                                            // Don't do anything here... the point is that we just don't break like in the else block.
                                        }
                                        else
                                        {
                                            break;
                                        }
                                    }

                                    // Are we done scanning the parameters?
                                    if (j == argsArray.Length - 1)
                                    {
                                        if (matchedMethod == null)
                                            matchedMethod = mi;
                                        else
                                            // More than one method is found with the specified name and specified parameters (which included at least one null).
                                            // This function shouldn't throw any errors, so return null and the caller should detect the reason.
                                            return null;
                                    }
                                }
                            }
                        }
                    }

                    // Did we fine one and only one method?
                    if (matchedMethod != null)
                        return matchedMethod; // return the only found method
                    else
                        return null; // no matching method
                }
            }

            // Find the matching method the normal way.
            Type[] parameterTypes;
            parameterTypes = Type.GetTypeArray(argsArray);

            return type.GetMethod(methodName, bindingAttr, null, parameterTypes, null);
        }

        private static Type[] getTypeArray(object[] args)
        {
            if (args == null)
            {
                throw new ArgumentNullException("args");
            }
            Type[] typeArray = new Type[args.Length];
            for (int i = 0; i < typeArray.Length; i++)
            {
                if (args[i] == null)
                {
                    throw new ArgumentNullException();
                }
                typeArray[i] = args[i].GetType();
            }
            return typeArray;
        }

        public static IScriptObject FromPrimitiveType(object value)
        {
            if (value is string)
                return new ScriptString((string)value);
            else if (IsNumericDataType(value.GetType()))
                return new ScriptNumber(Convert.ToDouble(value));
            else if (value is bool)
                return new ScriptBoolean((bool)value);
            else if (value is DateTime)
                return new ScriptDateTime((DateTime)value);
            else if (value is TimeSpan)
                return new ScriptTimeSpan((TimeSpan)value);
            else if (value is byte[])
                return new ScriptByteArray((byte[])value);
            //else if (value is System.Drawing.Image)
            //    return new ScriptImage(GraphicsHelper.BitmapToRawByteArray((System.Drawing.Image)value));
            else
                throw new ArgumentException("The value is not of a known primitive script object type.", "value");
        }

        public static bool IsPrimitiveType(object value)
        {
            if (value is string)
                return true;
            else if (IsNumericDataType(value.GetType()))
                return true;
            else if (value is bool)
                return true;
            else if (value is DateTime)
                return true;
            else if (value is byte[])
                return true;
            else
                return false;
        }

        protected static bool IsNumericDataType(System.Type type)
        {
            if (type == typeof(System.Decimal) ||
                type == typeof(System.Single) ||
                type == typeof(System.Double) ||
                type == typeof(System.Int16) ||
                type == typeof(System.Int32) ||
                type == typeof(System.Int64) ||
                type == typeof(System.UInt16) ||
                type == typeof(System.UInt32) ||
                type == typeof(System.UInt64) ||
                type == typeof(System.Byte) ||
                type == typeof(System.SByte))
                return true;

            return false;
        }

        public static bool IsScriptObject(object o)
        {
            return o is IScriptObject;
        }

        public static bool IsScriptObject(Type t)
        {
            return typeof(IScriptObject).IsAssignableFrom(t);
        }

        public static bool IsScriptArray(IScriptObject o)
        {
            return o is IScriptArray;
        }

        public static bool IsScriptArray(Type t)
        {
            return typeof(IScriptArray).IsAssignableFrom(t);
        }

        public virtual string ToDisplayString(ScriptObjectPropertyAttribute attribute)
        {
            return ScriptObject.ToDisplayString(this, attribute);
        }
        public static string ToDisplayString(IScriptObject o, ScriptObjectPropertyAttribute attribute)
        {
            string str = o.ToPrimitiveString();
            if (str == null)
                throw new InvalidOperationException("IScriptObject.ToDisplayString() can not convert '" + attribute.ParameterName + "' to a display string since the value is null.");
            return str;
        }


        //[Obsolete("Use type coersion instead, such as (bool) in C#.", true)]
        public virtual bool ToPrimitiveBoolean()
        {
            throw new Exception(this.TypeName + " can not be converted to a Boolean datatype.");
        }

        //[Obsolete("Use type coersion instead, such as (double) in C#.", true)]
        public virtual double ToPrimitiveDouble()
        {
            throw new Exception(this.TypeName + " can not be converted to a Number datatype.");
        }

        //[Obsolete("Use type coersion instead, such as (int) in C#.", true)]
        public virtual int ToPrimitiveInt32()
        {
            throw new Exception(this.TypeName + " can not be converted to a Number datatype.");
        }

        public virtual object ToPrimitiveObject()
        {
            throw new Exception(this.TypeName + " can not be converted to a primitive .net datatype.");
        }

        //[Obsolete("Use type coersion instead, such as (string) in C#.", true)]
        public virtual string ToPrimitiveString()
        {
            return this.ToString();
        }

        public string TypeName
        {
            get
            {
                return ScriptObject.GetShortTypeName(this.GetType().Name);
            }
        }

        public static string GetShortTypeName(Type t)
        {
            return GetShortTypeName(t.Name);
        }

        public static string GetShortTypeName(string typeName)
        {
            if (typeName == "ScriptArrayMarshalByValue") // this is an exception rule since we have 2 array types.
                return "Array";
            else if (typeName.StartsWith("Script"))
                return typeName.Substring(6);  // Strip off "Script" prefix.
            else
                return typeName;
        }
        
        public static Type[] GetScriptObjectTypes(bool includeObsolete, bool includeHidden)
        {
            Type iScriptObjectType = typeof(IScriptObject);
            Assembly assembly = iScriptObjectType.Assembly;

            Type[] types = null;
            try
            {
                types = assembly.GetTypes(); // This sometimes throws an OutOfMemoryException since I upgraded to .net 4. I don't know why yet.
            }
            catch (Exception ex)
            {
                throw new Exception("Failed to retrieve a list of Script Object types.", ex);
            }

            List<Type> items = new List<Type>();

            if (types != null)
            {
                foreach (Type type in types)
                {
                    //if (iScriptObjectType.IsAssignableFrom(type) && type != iScriptObjectType && type != typeof(ScriptMarshalByRefObject))
                    if (isTypeImplementationOf(type, iScriptObjectType) && type.IsAbstract == false)
                    {
                        if (includeObsolete == false)
                        {
                            // Don't use obsolete classes which throw a compiler error, such as the old ScriptArray class.
                            ObsoleteAttribute[] obsoleteAttribs = (ObsoleteAttribute[])type.GetCustomAttributes(typeof(ObsoleteAttribute), true);
                            if (obsoleteAttribs.Length > 0 && obsoleteAttribs[0].IsError)
                                continue;
                        }

                        if (includeHidden == false)
                        {
                            // Don't use classes which are marked as hidden.
                            ScriptObjectClassAttribute[] attribs = (ScriptObjectClassAttribute[])type.GetCustomAttributes(typeof(ScriptObjectClassAttribute), true);
                            if (attribs.Length > 0 && attribs[0].Hide)
                                continue;
                        }

                        items.Add(type);
                    }
                }
            }

            return items.ToArray();
        }

        /// <summary>
        /// Determines whether the specified System.Type implements the specified interface System.Type.
        /// This differs from Type.IsAssignableFrom() in that it returns false if theType and interfaceType are the same type.
        /// </summary>
        /// <param name="theType">The type to compare.</param>
        /// <param name="interfaceType">The interface type which theType must implement to return true.</param>
        /// <returns>true is theType implements interfaceType.</returns>
        private static bool isTypeImplementationOf(Type theType, Type interfaceType)
        {
            // The method is largely based on the internal .net method Type.ImplementInterface().

            if (interfaceType.IsInterface == false)
                throw new ArgumentException("ifaceType must be an interface type.");

            for (Type type = theType; type != null; type = type.BaseType) // this for loop seems unncessary but I use it since Type.ImplementInterface() uses it.
            {
                Type[] interfaces = type.GetInterfaces();
                if (interfaces != null)
                {
                    for (int i = 0; i < interfaces.Length; i++)
                    {
                        if ((interfaces[i] == interfaceType) || ((interfaces[i] != null) && isTypeImplementationOf(interfaces[i], interfaceType)))
                            return true;
                    }
                }
            }
            return false;
        }

        #endregion

        #region ISerializable Members

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("o", this, this.GetType());
        }

        #endregion
    }

    [Serializable]
    [DebuggerDisplay("ScriptNumber = {_number}")]
    [ScriptObjectClass("Number", "Represents a signed IEEE 64-bit (8-byte) double-precision floating-point number ranging in value from -1.79769313486231570E+308 through -4.94065645841246544E-324 for negative values and from 4.94065645841246544E-324 through 1.79769313486231570E+308 for positive values.")]
    public class ScriptNumber : ScriptObject, ISerializable
    {
        private double _number;

        [ScriptObjectConstructor("Number", "Initializes a new Number instance with a value of 0 (zero). This has the same affect as using a numeric literal with a value of zero.")]
        public ScriptNumber()
        {
            _number = 0;
        }
        public ScriptNumber(double value)
        {
            _number = value;
        }

        [ScriptObjectConstructor("Number", "Initializes a new Number instance to the specified number value.")]
        [ScriptObjectConstructorParameter("value", "A number.")]
        public ScriptNumber(ScriptNumber value)
        {
            _number = value._number;
        }

        [ScriptObjectConstructor("Number", "Initializes a new Number instance by converting a boolean to a number. false results in 0 (zero), and true results in 1.")]
        [ScriptObjectConstructorParameter("value", "A boolean.")]
        public ScriptNumber(ScriptBoolean value)
        {
            _number = (bool)value ? 1 : 0;
        }

        [ScriptObjectConstructor("Number", "Initializes a new Number instance by converting the specified string to a number.")]
        [ScriptObjectConstructorParameter("value", "A string.")]
        public ScriptNumber(ScriptString value)
        {
            _number = Convert.ToDouble((string)value);
        }

        // commented out since I added ToString() method accessible from scripts
        //public override string ToString()  // so we can see the value in the debugger
        //{
        //    return _number.ToString();
        //}

        public override string ToDisplayString(ScriptObjectPropertyAttribute attribute)
        {
            string s = null;
            if (attribute.HasNumberList)
            {
                int indexMatch = Array.IndexOf(attribute.GetNumericValues(), (double)this);
                if (indexMatch > -1)
                    s = attribute.GetNumericValueDisplayTexts()[indexMatch];
            }
            if (s == null)
                s = (string)this;

            return s;
        }

        public override string ToPrimitiveString()
        {
            return _number.ToString();
        }

        public string ToPrimitiveString(string format)
        {
            return _number.ToString(format);
        }

        public override double ToPrimitiveDouble()
        {
            return _number;
        }

        public override Int32 ToPrimitiveInt32()
        {
            return Convert.ToInt32(_number);
        }

        public override bool ToPrimitiveBoolean()
        {
            return Convert.ToBoolean(_number);
        }

        public override object ToPrimitiveObject()
        {
            return (object)_number;
        }

        [ScriptObjectMethodAttribute("To String","Converts the numeric value of this instance to it's equivalent string representation using the computers current locale format.")]
        public new ScriptString ToString()
        {
            return new ScriptString(_number.ToString());
        }

        [ScriptObjectMethodAttribute("To String with specified format", "Converts the numeric value of this instance to it's equivalent string representation using the specified format.")]
        [ScriptObjectMethodParameter("format", "A format string.")]
        public ScriptString ToString(ScriptString format)
        {
            return new ScriptString(_number.ToString((string)format));
        }

        public static explicit operator byte(ScriptNumber value)
        {
            return Convert.ToByte(value.ToPrimitiveObject());
        }

        public static explicit operator sbyte(ScriptNumber value)
        {
            return Convert.ToSByte(value.ToPrimitiveObject());
        }

        public static explicit operator short(ScriptNumber value)
        {
            return Convert.ToInt16(value.ToPrimitiveObject());
        }

        public static explicit operator ushort(ScriptNumber value)
        {
            return Convert.ToUInt16(value.ToPrimitiveObject());
        }

        public static explicit operator int(ScriptNumber value)
        {
            return Convert.ToInt32(value.ToPrimitiveObject());
        }

        public static explicit operator uint(ScriptNumber value)
        {
            return Convert.ToUInt32(value.ToPrimitiveObject());
        }

        public static explicit operator long(ScriptNumber value)
        {
            return Convert.ToInt64(value.ToPrimitiveObject());
        }

        public static explicit operator ulong(ScriptNumber value)
        {
            return Convert.ToUInt64(value.ToPrimitiveObject());
        }

        public static explicit operator decimal(ScriptNumber value)
        {
            return Convert.ToDecimal(value.ToPrimitiveObject());
        }

        //public static explicit operator double(ScriptNumber value)
        //{
        //    return (double)value.ToPrimitiveObject();
        //}

        public static implicit operator double(ScriptNumber value)
        {
            return (double)value.ToPrimitiveObject();
        }

        public static explicit operator float(ScriptNumber value)
        {
            return Convert.ToSingle(value.ToPrimitiveObject());
        }

        public static explicit operator string(ScriptNumber value)
        {
            return Convert.ToString(value.ToPrimitiveObject());
        }

        public static explicit operator bool(ScriptNumber value)
        {
            return Convert.ToBoolean(value.ToPrimitiveObject());
        }

        public static ScriptBoolean operator ==(ScriptNumber n1, ScriptNumber n2)
        {
            // If both are null, or both are same instance, return true.
            if (System.Object.ReferenceEquals(n1, n2))
                return ScriptBoolean.True;

            // If one is null, but not both, return false.
            if (((object)n1 == null) || ((object)n2 == null))
                return ScriptBoolean.False;

            // Return true if the fields match:
            return new ScriptBoolean((double)n1.ToPrimitiveObject() == (double)n2.ToPrimitiveObject());
        }

        public static ScriptBoolean operator !=(ScriptNumber n1, ScriptNumber n2)
        {
            // If both are null, or both are same instance, return true.
            if (System.Object.ReferenceEquals(n1, n2))
                return ScriptBoolean.False;

            // If one is null, but not both, return false.
            if (((object)n1 == null) || ((object)n2 == null))
                return ScriptBoolean.True;

            return new ScriptBoolean((double)n1.ToPrimitiveObject() != (double)n2.ToPrimitiveObject());
        }

        public static ScriptBoolean operator <(ScriptNumber n1, ScriptNumber n2)
        {
            return new ScriptBoolean((double)n1.ToPrimitiveObject() < (double)n2.ToPrimitiveObject());
        }

        public static ScriptBoolean operator >(ScriptNumber n1, ScriptNumber n2)
        {
            return new ScriptBoolean((double)n1.ToPrimitiveObject() > (double)n2.ToPrimitiveObject());
        }

        public static ScriptBoolean operator <=(ScriptNumber n1, ScriptNumber n2)
        {
            return new ScriptBoolean((double)n1.ToPrimitiveObject() <= (double)n2.ToPrimitiveObject());
        }

        public static ScriptBoolean operator >=(ScriptNumber n1, ScriptNumber n2)
        {
            return new ScriptBoolean((double)n1.ToPrimitiveObject() >= (double)n2.ToPrimitiveObject());
        }

        public static ScriptNumber operator +(ScriptNumber n1, ScriptNumber n2)
        {
            return new ScriptNumber((double)n1.ToPrimitiveObject() + (double)n2.ToPrimitiveObject());
        }

        public static ScriptNumber operator -(ScriptNumber n1, ScriptNumber n2)
        {
            return new ScriptNumber((double)n1.ToPrimitiveObject() - (double)n2.ToPrimitiveObject());
        }

        public static ScriptNumber operator *(ScriptNumber n1, ScriptNumber n2)
        {
            return new ScriptNumber((double)n1.ToPrimitiveObject() * (double)n2.ToPrimitiveObject());
        }

        public static ScriptNumber operator /(ScriptNumber n1, ScriptNumber n2)
        {
            return new ScriptNumber((double)n1.ToPrimitiveObject() / (double)n2.ToPrimitiveObject());
        }

        public override object Clone()
        {
            // This provides better performance than the base Clone().
            return new ScriptNumber(_number);
        }

        #region ISerializable Members
        protected ScriptNumber(SerializationInfo info, StreamingContext context)
        {
            if (info == null) throw new System.ArgumentNullException("info");
            _number = info.GetDouble("n");
        }

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("n", _number);
        }
        #endregion
    }

    [Serializable]
    [DebuggerDisplay("ScriptBoolean = {_boolean}")]
    [ScriptObjectClass("Boolean", "Represents a Boolean value of true or false.")]
    public class ScriptBoolean : ScriptObject, ISerializable
    {
        private bool _boolean;

        [ScriptObjectConstructor("Boolean", "Initializes a new instance of the Boolean class with a value of false.")]
        public ScriptBoolean()
            : this(false)
        {
        }

        public ScriptBoolean(bool value)
        {
            _boolean = value;
        }

        [ScriptObjectConstructor("Boolean", "Initializes a new instance of the Boolean class to the boolean representation of the specified string.")]
        [ScriptObjectConstructorParameter("value", "A string to convert to a boolean.")]
        public ScriptBoolean(ScriptString value)
        {
            // "true" (not case-sensitive) = true
            // "false" (not case-sensitive) = false
            // "junk" = errors
            _boolean = Convert.ToBoolean((string)value);
        }

        [ScriptObjectConstructor("Boolean", "Initializes a new instance of the Boolean class to the boolean representation of the specified number.")]
        [ScriptObjectConstructorParameter("value", "A number to convert to a boolean.")]
        public ScriptBoolean(ScriptNumber value)
        {
            // 0=false, otherwise true
            _boolean = Convert.ToBoolean((double)value);
        }

        [ScriptObjectConstructor("Boolean", "Initializes a new instance of the Boolean class to the specified value.")]
        [ScriptObjectConstructorParameter("value", "The value to assign to the boolean.")]
        public ScriptBoolean(ScriptBoolean value)
        {
            _boolean = value._boolean;
        }

        public ScriptBoolean(IScriptObject value)
        {
            _boolean = true; // any non ScriptString, ScriptNumber, ScriptBoolean sets it to true;
        }

        public static ScriptBoolean True
        {
            get
            {
                return new ScriptBoolean(true);
            }
        }

        public static ScriptBoolean False
        {
            get
            {
                return new ScriptBoolean(false);
            }
        }

        public override string ToString()  // so we can see the value in the debugger
        {
            return _boolean.ToString();
        }

        public override string ToPrimitiveString()
        {
            return _boolean.ToString().ToLower();
        }

        public override double ToPrimitiveDouble()
        {
            return Convert.ToDouble(_boolean);
        }

        public override Int32 ToPrimitiveInt32()
        {
            return Convert.ToInt32(_boolean);
        }

        public override bool ToPrimitiveBoolean()
        {
            return _boolean;
        }

        public override object ToPrimitiveObject()
        {
            return (object)_boolean;
        }

        public static explicit operator byte(ScriptBoolean value)
        {
            return Convert.ToByte(value.ToPrimitiveObject());
        }

        public static explicit operator sbyte(ScriptBoolean value)
        {
            return Convert.ToSByte(value.ToPrimitiveObject());
        }

        public static explicit operator short(ScriptBoolean value)
        {
            return Convert.ToInt16(value.ToPrimitiveObject());
        }

        public static explicit operator ushort(ScriptBoolean value)
        {
            return Convert.ToUInt16(value.ToPrimitiveObject());
        }

        public static explicit operator int(ScriptBoolean value)
        {
            return Convert.ToInt32(value.ToPrimitiveObject());
        }

        public static explicit operator uint(ScriptBoolean value)
        {
            return Convert.ToUInt32(value.ToPrimitiveObject());
        }

        public static explicit operator long(ScriptBoolean value)
        {
            return Convert.ToInt64(value.ToPrimitiveObject());
        }

        public static explicit operator ulong(ScriptBoolean value)
        {
            return Convert.ToUInt64(value.ToPrimitiveObject());
        }

        public static explicit operator decimal(ScriptBoolean value)
        {
            return Convert.ToDecimal(value.ToPrimitiveObject());
        }

        public static explicit operator double(ScriptBoolean value)
        {
            return Convert.ToSingle(value.ToPrimitiveObject());
        }

        public static explicit operator float(ScriptBoolean value)
        {
            return Convert.ToSingle(value.ToPrimitiveObject());
        }

        public static explicit operator string(ScriptBoolean value)
        {
            return Convert.ToString(value.ToPrimitiveObject());
        }

        //public static explicit operator bool(ScriptBoolean value)
        //{
        //    return (bool)value.ToPrimitiveObject();
        //}

        public static implicit operator bool(ScriptBoolean value)
        {
            return (bool)value.ToPrimitiveObject();
        }

        public override object Clone()
        {
            // This provides better performance than the base Clone().
            return new ScriptBoolean(_boolean);
        }

        #region ISerializable Members
        protected ScriptBoolean(SerializationInfo info, StreamingContext context)
        {
            if (info == null) throw new System.ArgumentNullException("info");
            _boolean = info.GetBoolean("b");
        }

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("b", _boolean);
        }
        #endregion
    }

    class CaseInsensitiveEqualityComparer : System.Collections.IEqualityComparer
    {
        bool System.Collections.IEqualityComparer.Equals(object x, object y)
        {
            return ((string)x).Equals((string)y, StringComparison.OrdinalIgnoreCase);
        }

        int System.Collections.IEqualityComparer.GetHashCode(object obj)
        {
            return ((string)obj).ToLower().GetHashCode();
        }
    }

    [Serializable]
    [DebuggerDisplay("ScriptDateTime = {_dateTime}")]
    [ScriptObjectClass("DateTime", "Represents an instant in time, typically expressed as a date and time of day.")]
    public class ScriptDateTime : ScriptObject, ISerializable
    {
        DateTime _dateTime;

        [ScriptObjectConstructor("DateTime", "Initializes a new DateTime instance to the current date and time.")]
        public ScriptDateTime() : this(DateTime.Now) { }

        public ScriptDateTime(DateTime value)
        {
            _dateTime = value;
        }

        [ScriptObjectConstructor("DateTime", "Initializes a new DateTime instance to the specified year, month, and day.")]
        [ScriptObjectConstructorParameter("year", "The year.")]
        [ScriptObjectConstructorParameter("month", "The month.")]
        [ScriptObjectConstructorParameter("day", "The numeric day of the month.")]
        public ScriptDateTime(ScriptNumber year, ScriptNumber month, ScriptNumber day) : this(new DateTime((int)year, (int)month, (int)day)) { }

        [ScriptObjectConstructor("DateTime", "Initializes a new DateTime instance to the specified year, month, day, hour, minute, and second.")]
        [ScriptObjectConstructorParameter("year", "The year.")]
        [ScriptObjectConstructorParameter("month", "The month.")]
        [ScriptObjectConstructorParameter("day", "The numeric day of the month.")]
        [ScriptObjectConstructorParameter("hour", "The hour.")]
        [ScriptObjectConstructorParameter("minute", "The minute.")]
        [ScriptObjectConstructorParameter("second", "The second.")]
        public ScriptDateTime(ScriptNumber year, ScriptNumber month, ScriptNumber day, ScriptNumber hour, ScriptNumber minute, ScriptNumber second) : this(new DateTime((int)year, (int)month, (int)day, (int)hour, (int)minute, (int)(int)second)) { }


        [ScriptObjectMethodAttribute("To String", "Converts the value of this instance to its equivalent string representation using the computers current format.")]
        public new ScriptString ToString()
        {
            return new ScriptString(_dateTime.ToString());
        }

        [ScriptObjectMethodAttribute("To String using specified format", "Converts the value of this instance to its equivalent string representation using the specified format.")]
        [ScriptObjectMethodParameter("format", "A format string.")]
        public ScriptString ToString(ScriptString format)
        {
            return new ScriptString(_dateTime.ToString((string)format));
        }

        [ScriptObjectPropertyAttribute("Day Of Week", "Gets the day of the week. 0=Sunday, 1=Monday, etc.")]
        public ScriptNumber DayOfWeek
        {
            get { return new ScriptNumber((int)_dateTime.DayOfWeek); }
        }

        [ScriptObjectPropertyAttribute("Day Of Year", "Gets the day of the year.")]
        public ScriptNumber DayOfYear
        {
            get { return new ScriptNumber(_dateTime.DayOfYear); }
        }

        [ScriptObjectPropertyAttribute("Day", "Gets the day component of the date.")]
        public ScriptNumber Day
        {
            get { return new ScriptNumber(_dateTime.Day); }
        }
        [ScriptObjectPropertyAttribute("Month", "Gets the month component of the date.")]
        public ScriptNumber Month
        {
            get { return new ScriptNumber(_dateTime.Month); }
        }
        [ScriptObjectPropertyAttribute("Year", "Gets the year component of the date.")]
        public ScriptNumber Year
        {
            get { return new ScriptNumber(_dateTime.Year); }
        }
        [ScriptObjectPropertyAttribute("Hour", "Gets the hour component of the date.")]
        public ScriptNumber Hour
        {
            get { return new ScriptNumber(_dateTime.Hour); }
        }
        [ScriptObjectPropertyAttribute("Minute", "Gets the minute component of the date.")]
        public ScriptNumber Minute
        {
            get { return new ScriptNumber(_dateTime.Minute); }
        }
        [ScriptObjectPropertyAttribute("Second", "Gets the second component of the date.")]
        public ScriptNumber Second
        {
            get { return new ScriptNumber(_dateTime.Second); }
        }
        [ScriptObjectPropertyAttribute("Millisecond", "Gets the millisecond component of the date.")]
        public ScriptNumber Millisecond
        {
            get { return new ScriptNumber(_dateTime.Millisecond); }
        }

        [ScriptObjectPropertyAttribute("TimeOfDay", "Gets the time of day for this instance.")]
        public ScriptTimeSpan TimeOfDay
        {
            get { return new ScriptTimeSpan(_dateTime.TimeOfDay); }
        }

        [ScriptObjectPropertyAttribute("IsWeekday", "Gets a boolean indicating if the date is a weekday (Monday through Friday).")]
        public ScriptBoolean IsWeekday
        {
            get { return new ScriptBoolean(_dateTime.DayOfWeek >= System.DayOfWeek.Monday && _dateTime.DayOfWeek <= System.DayOfWeek.Friday); }
        }

        [ScriptObjectPropertyAttribute("IsWeekend", "Gets a boolean indicating if the date is a weekend (Saturday or Sunday).")]
        public ScriptBoolean IsWeekend
        {
            get { return new ScriptBoolean(_dateTime.DayOfWeek == System.DayOfWeek.Saturday || _dateTime.DayOfWeek == System.DayOfWeek.Sunday); }
        }


        [ScriptObjectMethodAttribute("Add Days", "Adds the specified number of days to the value of this instance.")]
        [ScriptObjectMethodParameter("days", "A number of whole and fractional days. The value parameter can be negative or positive.")]
        public ScriptDateTime AddDays(ScriptNumber days)
        {
            return new ScriptDateTime(_dateTime.AddDays((double)days));
        }
        [ScriptObjectMethodAttribute("Add Hours", "Adds the specified number of hours to the value of this instance.")]
        [ScriptObjectMethodParameter("hours", "A number of whole and fractional hours. The value parameter can be negative or positive.")]
        public ScriptDateTime AddHours(ScriptNumber hours)
        {
            return new ScriptDateTime(_dateTime.AddHours((double)hours));
        }
        [ScriptObjectMethodAttribute("Add Milliseconds", "Adds the specified number of milliseconds to the value of this instance.")]
        [ScriptObjectMethodParameter("milliseconds", "A number of whole and fractional milliseconds. The value parameter can be negative or positive. Note that this value is rounded to the nearest integer.")]
        public ScriptDateTime AddMilliseconds(ScriptNumber milliseconds)
        {
            return new ScriptDateTime(_dateTime.AddMilliseconds((double)milliseconds));
        }
        [ScriptObjectMethodAttribute("Add Minutes", "Adds the specified number of minutes to the value of this instance.")]
        [ScriptObjectMethodParameter("minutes", "A number of whole and fractional minutes. The value parameter can be negative or positive.")]
        public ScriptDateTime AddMinutes(ScriptNumber minutes)
        {
            return new ScriptDateTime(_dateTime.AddMinutes((double)minutes));
        }
        [ScriptObjectMethodAttribute("Add Months", "Adds the specified number of months to the value of this instance.")]
        [ScriptObjectMethodParameter("months", "A number of months. The months parameter can be negative or positive.")]
        public ScriptDateTime AddMonths(ScriptNumber months)
        {
            return new ScriptDateTime(_dateTime.AddMonths((int)months));
        }
        [ScriptObjectMethodAttribute("Add Seconds", "Adds the specified number of seconds to the value of this instance.")]
        [ScriptObjectMethodParameter("seconds", "A number of whole and fractional seconds. The value parameter can be negative or positive.")]
        public ScriptDateTime AddSeconds(ScriptNumber seconds)
        {
            return new ScriptDateTime(_dateTime.AddSeconds((double)seconds));
        }
        [ScriptObjectMethodAttribute("Add Years", "Adds the specified number of years to the value of this instance.")]
        [ScriptObjectMethodParameter("years", "A number of years. The value parameter can be negative or positive.")]
        public ScriptDateTime AddYears(ScriptNumber years)
        {
            return new ScriptDateTime(_dateTime.AddYears((int)years));
        }
        [ScriptObjectMethodAttribute("Subtract", "Subtracts the specified date and time from this instance.")]
        [ScriptObjectMethodParameter("value", "An instance of DateTime.")]
        public ScriptTimeSpan Subtract(ScriptDateTime value)
        {
            return new ScriptTimeSpan(_dateTime.Subtract((DateTime)value));
        }
        [ScriptObjectMethodAttribute("Subtract", "Subtracts the specified duration from this instance.")]
        [ScriptObjectMethodParameter("value", "An instance of TimeSpan.")]
        public ScriptDateTime Subtract(ScriptTimeSpan value)
        {
            return new ScriptDateTime(_dateTime.Subtract((TimeSpan)value));
        }

        [ScriptObjectProperty("Now", "Gets a DateTime object that is set to the current date and time, expressed in local time.")]
        public static ScriptDateTime Now
        {
            get { return new ScriptDateTime(DateTime.Now); }
        }

        [ScriptObjectProperty("Today", "Gets a DateTime object that is set to the current date at midnight.")]
        public static ScriptDateTime Today
        {
            get { return new ScriptDateTime(DateTime.Today); }
        }

        public override string ToPrimitiveString()
        {
            return _dateTime.ToString();
        }
        public string ToPrimitiveString(string format)
        {
            return _dateTime.ToString(format);
        }
        public string ToPrimitiveString(string format, IFormatProvider provider)
        {
            return _dateTime.ToString(format, provider);
        }

        public override object ToPrimitiveObject()
        {
            return (object)_dateTime;
        }

        public static ScriptBoolean operator ==(ScriptDateTime d1, ScriptDateTime d2)
        {
            // If both are null, or both are same instance, return true.
            if (System.Object.ReferenceEquals(d1, d2))
                return ScriptBoolean.True;

            // If one is null, but not both, return false.
            if (((object)d1 == null) || ((object)d2 == null))
                return ScriptBoolean.False;

            return new ScriptBoolean((DateTime)d1.ToPrimitiveObject() == (DateTime)d2.ToPrimitiveObject());
        }

        public static ScriptBoolean operator !=(ScriptDateTime d1, ScriptDateTime d2)
        {
            // If both are null, or both are same instance, return true.
            if (System.Object.ReferenceEquals(d1, d2))
                return ScriptBoolean.False;

            // If one is null, but not both, return false.
            if (((object)d1 == null) || ((object)d2 == null))
                return ScriptBoolean.True;

            return new ScriptBoolean((DateTime)d1.ToPrimitiveObject() != (DateTime)d2.ToPrimitiveObject());
        }

        public static ScriptBoolean operator <(ScriptDateTime d1, ScriptDateTime d2)
        {
            return new ScriptBoolean((DateTime)d1.ToPrimitiveObject() < (DateTime)d2.ToPrimitiveObject());
        }

        public static ScriptBoolean operator >(ScriptDateTime d1, ScriptDateTime d2)
        {
            return new ScriptBoolean((DateTime)d1.ToPrimitiveObject() > (DateTime)d2.ToPrimitiveObject());
        }

        public static ScriptBoolean operator <=(ScriptDateTime d1, ScriptDateTime d2)
        {
            return new ScriptBoolean((DateTime)d1.ToPrimitiveObject() <= (DateTime)d2.ToPrimitiveObject());
        }

        public static ScriptBoolean operator >=(ScriptDateTime d1, ScriptDateTime d2)
        {
            return new ScriptBoolean((DateTime)d1.ToPrimitiveObject() >= (DateTime)d2.ToPrimitiveObject());
        }

        public static explicit operator string(ScriptDateTime value)
        {
            return Convert.ToString(value.ToPrimitiveObject());
        }

        //public static explicit operator DateTime(ScriptDateTime value)
        //{
        //    return (DateTime)(value.ToPrimitiveObject());
        //}

        public static implicit operator DateTime(ScriptDateTime value)
        {
            return (DateTime)(value.ToPrimitiveObject());
        }

        public override object Clone()
        {
            // This provides better performance than the base Clone().
            return new ScriptDateTime(_dateTime);
        }

        #region ISerializable Members
        protected ScriptDateTime(SerializationInfo info, StreamingContext context)
        {
            if (info == null) throw new System.ArgumentNullException("info");
            _dateTime = info.GetDateTime("d");
        }

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("d", _dateTime);
        }
        #endregion
    }


















    [Serializable]
    [DebuggerDisplay("ScriptTimeSpan = {_timeSpan}")]
    [ScriptObjectClass("TimeSpan", "Represents a time interval.")]
    public class ScriptTimeSpan : ScriptObject, ISerializable
    {
        TimeSpan _timeSpan;

        [ScriptObjectConstructor("DateTime", "Initializes a new DateTime instance to the current date and time.")]
        public ScriptTimeSpan()
        {
            _timeSpan = new TimeSpan();
        }

        public ScriptTimeSpan(TimeSpan value)
        {
            _timeSpan = value;
        }

        [ScriptObjectConstructor("TimeSpan", "Initializes a new TimeSpan to a specified number of hours, minutes, and seconds.")]
        [ScriptObjectConstructorParameter("hours", "Number of hours")]
        [ScriptObjectConstructorParameter("minutes", "Number of minutes.")]
        [ScriptObjectConstructorParameter("seconds", "Number of seconds.")]
        public ScriptTimeSpan(ScriptNumber hours, ScriptNumber minutes, ScriptNumber seconds)
            : this(new TimeSpan((int)hours, (int)minutes, (int)seconds)) { }

        [ScriptObjectConstructor("TimeSpan", "Initializes a new TimeSpan to a specified number of days, hours, minutes, and seconds.")]
        [ScriptObjectConstructorParameter("days", "Number of days.")]
        [ScriptObjectConstructorParameter("hours", "Number of hours")]
        [ScriptObjectConstructorParameter("minutes", "Number of minutes.")]
        [ScriptObjectConstructorParameter("seconds", "Number of seconds.")]
        public ScriptTimeSpan(ScriptNumber days, ScriptNumber hours, ScriptNumber minutes, ScriptNumber seconds)
            : this(new TimeSpan((int)days, (int)hours, (int)minutes, (int)seconds)) { }

        [ScriptObjectConstructor("TimeSpan", "Initializes a new TimeSpan to a specified number of days, hours, minutes, seconds, milliseconds.")]
        [ScriptObjectConstructorParameter("days", "Number of days.")]
        [ScriptObjectConstructorParameter("hours", "Number of hours")]
        [ScriptObjectConstructorParameter("minutes", "Number of minutes.")]
        [ScriptObjectConstructorParameter("seconds", "Number of seconds.")]
        [ScriptObjectConstructorParameter("milliseconds", "Number of milliseconds.")]
        public ScriptTimeSpan(ScriptNumber days, ScriptNumber hours, ScriptNumber minutes, ScriptNumber seconds, ScriptNumber milliseconds)
            : this(new TimeSpan((int)days, (int)hours, (int)minutes, (int)seconds, (int)milliseconds)) { }


        [ScriptObjectMethodAttribute("To String", "Converts the value of this instance to its equivalent string representation using the computers current format.")]
        public new ScriptString ToString()
        {
            string s = toString();

            return new ScriptString(s);
        }

        private string toString()
        {
            // The default ToString() uses the following format "[-][d.][hh:]mm:ss[.fffffff]", but I don't want it to always return hour
            // and I don't want the 1st value to be zero filled so I wrote custom logic below...

            string s = "";

            if (_timeSpan.Days != 0)
                s += Math.Abs(_timeSpan.Days) + ".";

            if (_timeSpan.Hours != 0)
            {
                if (s.Length > 0)
                    s += Math.Abs(_timeSpan.Hours).ToString("00") + ":";
                else
                    s += Math.Abs(_timeSpan.Hours).ToString("0") + ":";
            }

            if (s.Length > 0)
                s += Math.Abs(_timeSpan.Minutes).ToString("00") + ":";
            else
                s += Math.Abs(_timeSpan.Minutes).ToString("0") + ":";

            s += Math.Abs(_timeSpan.Seconds).ToString("00");

            if (_timeSpan.Milliseconds != 0)
                s += "." + Math.Abs(_timeSpan.Milliseconds).ToString("000");

            // Add the minus last since we check the string length above.
            if (_timeSpan < TimeSpan.Zero)
                s = "-" + s;

            return s;
        }

        [ScriptObjectMethodAttribute("To String using specified format", "Converts the value of this instance to its equivalent string representation using the specified format.")]
        [ScriptObjectMethodParameter("format", "A format string.")]
        public ScriptString ToString(ScriptString format)
        {
            string s = (string)format;

            if (s == "c")
                //s = "[-][d.]hh:mm:ss[.fffffff]";
                return new ScriptString(_timeSpan.ToString());

            s = s.Replace("d", _timeSpan.Days.ToString());
            s = s.Replace("hh", _timeSpan.Hours.ToString());
            s = s.Replace("mm", _timeSpan.Minutes.ToString());
            s = s.Replace("ss", _timeSpan.Seconds.ToString());
            s = s.Replace("fffffff", _timeSpan.Milliseconds.ToString("#######"));
            
            string minus = (_timeSpan < TimeSpan.Zero) ? "-" : "";
            s = s.Replace("-", minus);

            return new ScriptString(s);
        }

        public static ScriptTimeSpan FromSeconds(double seconds) // for internal use
        {
            return new ScriptTimeSpan(TimeSpan.FromSeconds(seconds));
        }

        [ScriptObjectMethod("From Hours", "Returns a TimeSpan that represents a specified number of hours where the specification is accurate to the nearest millisecond.")]
        [ScriptObjectMethodParameter("Seconds", "A number of hours, accurate to the nearest millisecond.")]
        public static ScriptTimeSpan FromHours(ScriptNumber hours)
        {
            return new ScriptTimeSpan(TimeSpan.FromHours(hours));
        }

        [ScriptObjectMethod("From Minutes", "Returns a TimeSpan that represents a specified number of minutes where the specification is accurate to the nearest millisecond.")]
        [ScriptObjectMethodParameter("Minutes", "A number of minutes, accurate to the nearest millisecond.")]
        public static ScriptTimeSpan FromMinutes(ScriptNumber minutes)
        {
            return new ScriptTimeSpan(TimeSpan.FromMinutes(minutes));
        }

        [ScriptObjectMethod("From Seconds", "Returns a TimeSpan that represents a specified number of seconds where the specification is accurate to the nearest millisecond.")]
        [ScriptObjectMethodParameter("Seconds", "A number of seconds, accurate to the nearest millisecond.")]
        public static ScriptTimeSpan FromSeconds(ScriptNumber seconds)
        {
            return new ScriptTimeSpan(TimeSpan.FromSeconds(seconds));
        }

        [ScriptObjectMethod("From Milliseconds", "Returns a TimeSpan that represents a specified number of milliseconds.")]
        [ScriptObjectMethodParameter("Milliseconds", "A number of milliseconds.")]
        public static ScriptTimeSpan FromMilliseconds(ScriptNumber milliseconds)
        {
            return new ScriptTimeSpan(TimeSpan.FromMilliseconds(milliseconds));
        }

        [ScriptObjectPropertyAttribute("Days", "Gets the number of whole days represented by the current TimeSpan.")]
        public ScriptNumber Days
        {
            get { return new ScriptNumber(_timeSpan.Days); }
        }
        [ScriptObjectPropertyAttribute("Hours", "Gets the number of whole hours represented by the current TimeSpan.")]
        public ScriptNumber Hours
        {
            get { return new ScriptNumber(_timeSpan.Hours); }
        }
        [ScriptObjectPropertyAttribute("Minutes", "Gets the number of whole minutes represented by the current TimeSpan.")]
        public ScriptNumber Minutes
        {
            get { return new ScriptNumber(_timeSpan.Minutes); }
        }
        [ScriptObjectPropertyAttribute("Seconds", "Gets the number of whole seconds represented by the current TimeSpan.")]
        public ScriptNumber Seconds
        {
            get { return new ScriptNumber(_timeSpan.Seconds); }
        }
        [ScriptObjectPropertyAttribute("Milliseconds", "Gets the number of whole milliseconds represented by the current TimeSpan.")]
        public ScriptNumber Milliseconds
        {
            get { return new ScriptNumber(_timeSpan.Milliseconds); }
        }

        [ScriptObjectPropertyAttribute("Total Days", "Gets the value of the TimeSpan expressed in whole and fractional days.")]
        public ScriptNumber TotalDays
        {
            get { return new ScriptNumber(_timeSpan.TotalDays); }
        }
        [ScriptObjectPropertyAttribute("Total Hours", "Gets the value of the TimeSpan expressed in whole and fractional hours.")]
        public ScriptNumber TotalHours
        {
            get { return new ScriptNumber(_timeSpan.TotalHours); }
        }
        [ScriptObjectPropertyAttribute("Total Minutes", "Gets the value of the TimeSpan expressed in whole and fractional minutes.")]
        public ScriptNumber TotalMinutes
        {
            get { return new ScriptNumber(_timeSpan.TotalMinutes); }
        }
        [ScriptObjectPropertyAttribute("Total Seconds", "Gets the value of the TimeSpan expressed in whole and fractional seconds.")]
        public ScriptNumber TotalSeconds
        {
            get { return new ScriptNumber(_timeSpan.TotalSeconds); }
        }
        [ScriptObjectPropertyAttribute("Total Milliseconds", "Gets the value of the TimeSpan expressed in whole and fractional milliseconds.")]
        public ScriptNumber TotalMilliseconds
        {
            get { return new ScriptNumber(_timeSpan.TotalMilliseconds); }
        }


        [ScriptObjectMethodAttribute("Add", "Adds the specified TimeSpan to this instance.")]
        [ScriptObjectMethodParameter("TimeSpan", "A TimeSpan.")]
        public ScriptTimeSpan Add(ScriptTimeSpan timeSpan)
        {
            return new ScriptTimeSpan(_timeSpan.Add((TimeSpan)timeSpan));
        }
        [ScriptObjectMethodAttribute("Duration", "Returns a new TimeSpan whose value is the absolute value of this instance.")]
        public ScriptTimeSpan Duration()
        {
            return new ScriptTimeSpan(_timeSpan.Duration());
        }
        [ScriptObjectMethodAttribute("Negate", "Returns a new TimeSpan whose value is the negated value of this instance.")]
        public ScriptTimeSpan Negate()
        {
            return new ScriptTimeSpan(_timeSpan.Negate());
        }
        [ScriptObjectMethodAttribute("Subtract", "Subtracts the specified TimeSpan from this instance.")]
        [ScriptObjectMethodParameter("TimeSpan", "A TimeSpan.")]
        public ScriptTimeSpan Subtract(ScriptTimeSpan timeSpan)
        {
            return new ScriptTimeSpan(_timeSpan.Subtract((TimeSpan)timeSpan));
        }

        public override string ToPrimitiveString()
        {
            return toString();
        }
        //public string ToPrimitiveString(string format)
        //{
        //    return _timeSpan.ToString(format);
        //}

        public override object ToPrimitiveObject()
        {
            return (object)_timeSpan;
        }

        public static ScriptBoolean operator ==(ScriptTimeSpan ts1, ScriptTimeSpan ts2)
        {
            // If both are null, or both are same instance, return true.
            if (System.Object.ReferenceEquals(ts1, ts2))
                return ScriptBoolean.True;

            // If one is null, but not both, return false.
            if (((object)ts1 == null) || ((object)ts2 == null))
                return ScriptBoolean.False;

            return new ScriptBoolean((TimeSpan)ts1.ToPrimitiveObject() == (TimeSpan)ts2.ToPrimitiveObject());
        }

        public static ScriptBoolean operator !=(ScriptTimeSpan ts1, ScriptTimeSpan ts2)
        {
            // If both are null, or both are same instance, return true.
            if (System.Object.ReferenceEquals(ts1, ts2))
                return ScriptBoolean.False;

            // If one is null, but not both, return false.
            if (((object)ts1 == null) || ((object)ts2 == null))
                return ScriptBoolean.True;

            return new ScriptBoolean((TimeSpan)ts1.ToPrimitiveObject() != (TimeSpan)ts2.ToPrimitiveObject());
        }

        public static ScriptBoolean operator <(ScriptTimeSpan d1, ScriptTimeSpan d2)
        {
            return new ScriptBoolean((TimeSpan)d1.ToPrimitiveObject() < (TimeSpan)d2.ToPrimitiveObject());
        }

        public static ScriptBoolean operator >(ScriptTimeSpan d1, ScriptTimeSpan d2)
        {
            return new ScriptBoolean((TimeSpan)d1.ToPrimitiveObject() > (TimeSpan)d2.ToPrimitiveObject());
        }

        public static ScriptBoolean operator <=(ScriptTimeSpan d1, ScriptTimeSpan d2)
        {
            return new ScriptBoolean((TimeSpan)d1.ToPrimitiveObject() <= (TimeSpan)d2.ToPrimitiveObject());
        }

        public static ScriptBoolean operator >=(ScriptTimeSpan d1, ScriptTimeSpan d2)
        {
            return new ScriptBoolean((TimeSpan)d1.ToPrimitiveObject() >= (TimeSpan)d2.ToPrimitiveObject());
        }

        public static explicit operator string(ScriptTimeSpan value)
        {
            return Convert.ToString(value.ToPrimitiveObject());
        }

        //public static explicit operator DateTime(ScriptDateTime value)
        //{
        //    return (DateTime)(value.ToPrimitiveObject());
        //}

        public static implicit operator TimeSpan(ScriptTimeSpan value)
        {
            return (TimeSpan)(value.ToPrimitiveObject());
        }

        public override object Clone()
        {
            // This provides better performance than the base Clone().
            return new ScriptTimeSpan(_timeSpan);
        }

        #region ISerializable Members
        protected ScriptTimeSpan(SerializationInfo info, StreamingContext context)
        {
            if (info == null) throw new System.ArgumentNullException("info");
            _timeSpan = new TimeSpan(info.GetInt64("ts"));
        }
        
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("ts", _timeSpan.Ticks);
        }
        #endregion
    }



















    [ScriptObjectClass("Math", "Provides constants and static methods for logarithmic, and other common mathematical functions. This object can not be instantiated in a script.")]
    public class ScriptMath : ScriptObject
    {
        private static Random _random = new Random();

        private ScriptMath()  // Prevent the user from creating an instance of this type.
        {
        }

        [ScriptObjectMethodAttribute("Absolute Value", "Returns the absolute value of a number.")]
        public static ScriptNumber Abs(ScriptNumber value)
        {
            return new ScriptNumber(System.Math.Abs((double)value));
        }
        [ScriptObjectMethodAttribute("Round", "Rounds a number to a specified precision.")]
        [ScriptObjectMethodParameter("value", "The number to be rounded.")]
        [ScriptObjectMethodParameter("digits", "The number of significant decimal places (precision) in the return value.")]
        public static ScriptNumber Round(ScriptNumber value, ScriptNumber digits)
        {
            return new ScriptNumber(System.Math.Round((double)value, (int)digits));
        }
        [ScriptObjectMethodAttribute("Power", "Returns a number raised to the specified power.")]
        [ScriptObjectMethodParameter("x", "A number to be raised to a power.")]
        [ScriptObjectMethodParameter("power", "A number specifying the power.")]
        public static ScriptNumber Pow(ScriptNumber x, ScriptNumber power)
        {
            return new ScriptNumber(System.Math.Pow((double)x, (double)power));
        }
        [ScriptObjectMethodAttribute("Truncate", "Calculates the integral part of a specified number, that is, the number that remains after any fractional digits have been discarded.")]
        [ScriptObjectMethodParameter("value", "A number.")]
        public static ScriptNumber Truncate(ScriptNumber value)
        {
            return new ScriptNumber(System.Math.Truncate((double)value));
        }
        [ScriptObjectMethodAttribute("Sign", "Returns a value indicating the sign on a number.")]
        [ScriptObjectMethodParameter("value", "A number.")]
        public static ScriptNumber Sign(ScriptNumber value)
        {
            return new ScriptNumber(System.Math.Sign((double)value));
        }
        [ScriptObjectMethodAttribute("Floor", "Returns the largest number less than or equal to the specified number.")]
        [ScriptObjectMethodParameter("value", "A number.")]
        public static ScriptNumber Floor(ScriptNumber value)
        {
            return new ScriptNumber(System.Math.Floor((double)value));
        }
        [ScriptObjectMethodAttribute("Ceiling", "Returns the smallest number greater than or equal to the specified number.")]
        [ScriptObjectMethodParameter("value", "A number.")]
        public static ScriptNumber Ceiling(ScriptNumber value)
        {
            return new ScriptNumber(System.Math.Ceiling((double)value));
        }
        [ScriptObjectMethodAttribute("E Power", "Returns e raised to the specified power.")]
        [ScriptObjectMethodParameter("power", "A number specifying the power.")]
        public static ScriptNumber Exp(ScriptNumber power)
        {
            return new ScriptNumber(System.Math.Exp((double)power));
        }
        [ScriptObjectMethodAttribute("Minimum", "Returns the smaller of two numbers.")]
        [ScriptObjectMethodParameter("value1", "The first of two numbers to compare.")]
        [ScriptObjectMethodParameter("value2", "The second of two numbers to compare.")]
        public static ScriptNumber Min(ScriptNumber value1, ScriptNumber value2)
        {
            return new ScriptNumber(System.Math.Min((double)value1, (double)value2));
        }
        [ScriptObjectMethodAttribute("Maximum", "Returns the larger of two numbers.")]
        [ScriptObjectMethodParameter("value1", "The first of two numbers to compare.")]
        [ScriptObjectMethodParameter("value2", "The second of two numbers to compare.")]
        public static ScriptNumber Max(ScriptNumber value1, ScriptNumber value2)
        {
            return new ScriptNumber(System.Math.Max((double)value1, (double)value2));
        }
        [ScriptObjectMethodAttribute("Logarithm base 10", "Returns the base 10 logarithm of a specified number.")]
        [ScriptObjectMethodParameter("value", "A number whose logarithm is to be found.")]
        public static ScriptNumber Log10(ScriptNumber value)
        {
            return new ScriptNumber(System.Math.Log10((double)value));
        }
        [ScriptObjectMethodAttribute("Logarithm", "Returns the natural (base e) logarithm of a specified number.")]
        [ScriptObjectMethodParameter("value", "A number whose logarithm is to be found.")]
        public static ScriptNumber Log(ScriptNumber value)
        {
            return new ScriptNumber(System.Math.Log((double)value));
        }
        [ScriptObjectMethodAttribute("Logarithm", "Returns the logarithm of a specified number in a specified base.")]
        [ScriptObjectMethodParameter("value", "A number whose logarithm is to be found.")]
        [ScriptObjectMethodParameter("newBase", "The base of the logarithm.")]
        public static ScriptNumber Log(ScriptNumber value, ScriptNumber newBase)
        {
            return new ScriptNumber(System.Math.Log((double)value, (double)newBase));
        }
        [ScriptObjectPropertyAttribute("PI", "Represents the ratio of the circumferance of a circle to its diameter.")]
        public static ScriptNumber PI
        {
            get { return new ScriptNumber(System.Math.PI); }
        }
        [ScriptObjectPropertyAttribute("E", "Represents the natural logarithmic base, specified by the constant, e.")]
        public static ScriptNumber E
        {
            get { return new ScriptNumber(System.Math.E); }
        }

        [ScriptObjectMethodAttribute("Get Random Integer", "Gets a random integer within a specified range.")]
        [ScriptObjectMethodParameter("min", "The inclusive lower bound of the random number returned.")]
        [ScriptObjectMethodParameter("max", "The exclusive upper bound of the random number returned. max must be greater than or equal to min.")]
        public static ScriptNumber GetRandomInteger(ScriptNumber min, ScriptNumber max)
        {
            return new ScriptNumber(_random.Next((int)min, (int)max));
        }

        [ScriptObjectMethodAttribute("Get Random Number", "Gets a random real number greater than or equal to 0.0, and less than 1.0.")]
        public static ScriptNumber GetRandomNumber()
        {
            return new ScriptNumber(_random.NextDouble());
        }

        [ScriptObjectMethodAttribute("Is Odd Number", "Gets a value indicating if a number if odd.")]
        [ScriptObjectMethodParameter("value", "A number.")]
        public static ScriptBoolean IsOddNumber(ScriptNumber value)
        {
            double d = (double)value;
            return new ScriptBoolean(d % 2 != 0);
        }

        [ScriptObjectMethodAttribute("Is Even Number", "Gets a value indicating if a number if even.")]
        [ScriptObjectMethodParameter("value", "A number.")]
        public static ScriptBoolean IsEvenNumber(ScriptNumber value)
        {
            double d = (double)value;
            return new ScriptBoolean(d % 2 == 0);
        }

        [ScriptObjectMethodAttribute("Is Number Multiple Of", "Gets a value indicating if a number is a multple of another number.")]
        [ScriptObjectMethodParameter("value", "A number.")]
        [ScriptObjectMethodParameter("multiple", "A number.")]
        public static ScriptBoolean IsNumberMultipleOf(ScriptNumber value, ScriptNumber multiple)
        {
            double d = (double)value;
            double m = (double)multiple;
            return new ScriptBoolean(d % multiple == 0);
        }
    }

    /// <summary>
    /// Exposes members for array-like Script Object collections.
    /// </summary>
    public interface IScriptArray : IScriptObject
    {
        ScriptNumber BoundsLength { get; }
        ScriptBoolean CanRead { get; }
        ScriptBoolean CanWrite { get; }
        ScriptNumber HighestIndex { get; }
        ScriptNumber Length { get; }
        ScriptNumber LowestIndex { get; }

        int PrimitiveBoundsLength { get; }
        int PrimitiveHighestIndex { get; }
        int PrimitiveLength { get; }
        int PrimitiveLowestIndex { get; }
        byte[] SerializeAsBaseArray();
        void SetItemInternal(int index, IScriptObject o);
        
        IScriptObject this[int index] { get; set; }
        IScriptArray ToBaseScriptArray();

        ScriptNumber Add(IScriptObject value);
        void Clear();
        ScriptBoolean Contains(IScriptObject value);
        ScriptNumber IndexOf(IScriptObject value);
        void Insert(int index, IScriptObject value);
        void Insert(ScriptNumber index, IScriptObject value);
        void Remove(IScriptObject value);
        void RemoveAt(int index);
        void RemoveAt(ScriptNumber index);
    }

    public delegate void ScriptArraySetDoubleCallback(int index, double value);
    public delegate void ScriptArraySetInt32Callback(int index, int value);
    public delegate void ScriptArraySetStringCallback(int index, string value);
    public delegate void ScriptArraySetBooleanCallback(int index, bool value);
    public delegate void ScriptArraySetDateTimeCallback(int index, DateTime value);
    public delegate void ScriptArraySetTimeSpanCallback(int index, TimeSpan value);
    public delegate void ScriptArraySetScriptObjectCallback(int index, IScriptObject value);

    public delegate void ScriptArraySetScriptNumberCallback(ScriptNumber index, ScriptNumber value);
    public delegate void ScriptArraySetScriptStringCallback(ScriptNumber index, ScriptString value);
    public delegate void ScriptArraySetScriptBooleanCallback(ScriptNumber index, ScriptBoolean value);
    public delegate void ScriptArraySetScriptDateTimeCallback(ScriptNumber index, ScriptDateTime value);
    public delegate void ScriptArraySetScriptTimeSpanCallback(ScriptNumber index, ScriptTimeSpan value);

    [Obsolete(
    "Use the ScriptArrayMarshalByReference or ScriptArrayMarshalByValue class. The ScriptArrayMarshalByReference class is the same as the obsolete ScriptArray class.\r\n" +
    "Use the ScriptArrayMarshalByValue for immutable arrays or arrays which do not need to send a set index value back to the driver. For example a read only list of zone names. This gives the best performance (10 times faster tan ScriptArrayMarshalByReference) but index value changes are not detected by the source driver.\r\n" +
    "Use the ScriptArrayMarshalByReference class for arrays which use write callbacks or subclass arrays which update the driver when an index value is set. For example use this for a writable array returned from a driver's ZoneVolume property. When an index value is set the driver will be notified of the change so that the actual volume of the associated device can be updated."
    , true)]
    public class ScriptArray : ScriptArrayMarshalByValue, IScriptArray, ISerializable
    {
    }

    [Serializable]
    [ScriptObjectClass("ArrayArrayMarshalByReference", "The Array object is used to store a set of values in a single variable name. Arrays are zero indexed meaning the first element is at index zero. Elements in arrays can be different types. The array elements are accessed through computed indexes. Square brackets [...] are used in the script to enclose the index number. " +
        "Use the ScriptArrayMarshalByReference class for arrays which use write callbacks or subclass arrays which update the driver when an index value is set. For example use this for a writable array returned from a driver's ZoneVolume property. When an index value is set the driver will be notified of the change so that the actual volume of the associated device can be updated.",
        true)]
    public class ScriptArrayMarshalByReference : ScriptMarshalByRefObject, IScriptArray, ISerializable
    {
        protected List<IScriptObject> _list;
        protected bool _isReadable;
        protected bool _isWriteable;

        protected int _lowestIndex = -1; // the lowest element index that has been assigned a value.
        protected int _highestIndex = -1; // the highest element index that has been assigned a value.

        protected bool _allowSettingOutOfRangeIndexes;

        protected ScriptArraySetDoubleCallback _setCallbackDouble;
        protected ScriptArraySetInt32Callback _setCallbackInt32;
        protected ScriptArraySetStringCallback _setCallbackString;
        protected ScriptArraySetBooleanCallback _setCallbackBoolean;
        protected ScriptArraySetDateTimeCallback _setCallbackDateTime;
        protected ScriptArraySetTimeSpanCallback _setCallbackTimeSpan;
        protected ScriptArraySetScriptObjectCallback _setCallbackScriptObject;

        protected ScriptArraySetScriptNumberCallback _setCallbackScriptNumber;
        protected ScriptArraySetScriptStringCallback _setCallbackScriptString;
        protected ScriptArraySetScriptBooleanCallback _setCallbackScriptBoolean;
        protected ScriptArraySetScriptDateTimeCallback _setCallbackScriptDateTime;
        protected ScriptArraySetScriptTimeSpanCallback _setCallbackScriptTimeSpan;

        /// <summary>
        /// Initializes a new empty readable and writeable instance of the ScriptArray class. Use the SetItemInternal method to populate.
        /// </summary>
        [ScriptObjectConstructor("Array", "Initializes a new instance of the Array class.")]
        public ScriptArrayMarshalByReference()
            : this(null, true, true)
        {
        }

        /// <summary>
        /// Initializes a new empty instance of the ScriptArray class with the specified read/write accessibility. Use the SetItemInternal method to populate.
        /// </summary>
        /// <param name="isReadable">Indicates if the ScriptArray elements can be read.</param>
        /// <param name="isWriteable">Indicates if the ScriptArray elements can be written to.</param>
        public ScriptArrayMarshalByReference(bool isReadable, bool isWriteable)
            : this(null, isReadable, isWriteable)
        {
        }

        /// <summary>
        /// Initializes a new instance of the ScriptArray class populated with the given ScriptObjects starting at index 0.
        /// </summary>
        /// <param name="items"></param>
        /// <param name="isReadable">Indicates if the ScriptArray elements can be read.</param>
        /// <param name="isWriteable">Indicates if the ScriptArray elements can be written to.</param>
        public ScriptArrayMarshalByReference(IEnumerable<IScriptObject> items, bool isReadable, bool isWriteable)
        {
            if (items != null)
            {
                _list = new List<IScriptObject>(items);
                _lowestIndex = 0;
                _highestIndex = _list.Count - 1;
            }
            else
                _list = new List<IScriptObject>();
            
            _isReadable = isReadable;
            _isWriteable = isWriteable;
            _allowSettingOutOfRangeIndexes = true;
        }

        /// <summary>
        /// Initializes a new instance of the ScriptArray class that contains ScriptNumber elements copied from the specified System.Double collection.
        /// </summary>
        /// <param name="indexValues">The collection whose elements are copied to the new ScriptArray. Each System.Double will be converted to a ScriptNumber.</param>
        /// <param name="baseIndex">The index of the first element in the resulting ScriptArray. This is usually 0 or 1.</param>
        public ScriptArrayMarshalByReference(IEnumerable<double> indexValues, int baseIndex)
        {
            _list = new List<IScriptObject>();
            _isReadable = true;
            _isWriteable = false;
            _allowSettingOutOfRangeIndexes = false;

            int i = baseIndex;
            foreach (double value in indexValues)
                this.SetItemInternal(i++, new ScriptNumber(value));
        }
        /// <summary>
        /// Initializes a new instance of the ScriptArray class that contains ScriptNumber elements copied from the specified System.Double collection.
        /// </summary>
        /// <param name="indexValues">The collection whose elements are copied to the new ScriptArray. Each System.Double will be converted to a ScriptNumber.</param>
        /// <param name="setArrayElementCallback">A ScriptArraySetDoubleCallback delegate that contains a method to be called when an array element is set. Calling the SetItemInternal method will not invoke the callback.</param>
        /// <param name="baseIndex">The index of the first element in the resulting ScriptArray. This is usually 0 or 1.</param>
        public ScriptArrayMarshalByReference(IEnumerable<double> indexValues, ScriptArraySetDoubleCallback setArrayElementCallback, int baseIndex)
        {
            _setCallbackDouble = setArrayElementCallback;
            _list = new List<IScriptObject>();
            _isReadable = true;
            _isWriteable = setArrayElementCallback != null;
            _allowSettingOutOfRangeIndexes = false;

            int i = baseIndex;
            foreach (double value in indexValues)
                this.SetItemInternal(i++, new ScriptNumber(value));
        }
        /// <summary>
        /// Initializes a new instance of the ScriptArray class that contains ScriptNumber elements copied from the specified System.Double collection.
        /// </summary>
        /// <param name="indexValues">The collection whose elements are copied to the new ScriptArray. Each System.Double will be converted to a ScriptNumber.</param>
        /// <param name="setArrayElementCallback">A ScriptArraySetScriptNumberCallback delegate that contains a method to be called when an array element is set. Calling the SetItemInternal method will not invoke the callback.</param>
        /// <param name="baseIndex">The index of the first element in the resulting ScriptArray. This is usually 0 or 1.</param>
        public ScriptArrayMarshalByReference(IEnumerable<double> indexValues, ScriptArraySetScriptNumberCallback setArrayElementCallback, int baseIndex)
        {
            _setCallbackScriptNumber = setArrayElementCallback;
            _list = new List<IScriptObject>();
            _isReadable = true;
            _isWriteable = setArrayElementCallback != null;
            _allowSettingOutOfRangeIndexes = false;

            int i = baseIndex;
            foreach (double value in indexValues)
                this.SetItemInternal(i++, new ScriptNumber(value));
        }

        /// <summary>
        /// Initializes a new instance of the ScriptArray class that contains ScriptNumber elements copied from the specified System.Int32 collection.
        /// </summary>
        /// <param name="indexValues">The collection whose elements are copied to the new ScriptArray. Each System.Int32 will be converted to a ScriptNumber.</param>
        /// <param name="baseIndex">The index of the first element in the resulting ScriptArray. This is usually 0 or 1.</param>
        public ScriptArrayMarshalByReference(IEnumerable<int> indexValues, int baseIndex)
        {
            _list = new List<IScriptObject>();
            _isReadable = true;
            _isWriteable = false;
            _allowSettingOutOfRangeIndexes = false;

            int i = baseIndex;
            foreach (int value in indexValues)
                this.SetItemInternal(i++, new ScriptNumber(value));
        }
        /// <summary>
        /// Initializes a new instance of the ScriptArray class that contains ScriptNumber elements copied from the specified System.Int32 collection.
        /// </summary>
        /// <param name="indexValues">The collection whose elements are copied to the new ScriptArray. Each System.Int32 will be converted to a ScriptNumber.</param>
        /// <param name="setArrayElementCallback">A ScriptArraySetInt32Callback delegate that contains a method to be called when an array element is set. Calling the SetItemInternal method will not invoke the callback.</param>
        /// <param name="baseIndex">The index of the first element in the resulting ScriptArray. This is usually 0 or 1.</param>
        public ScriptArrayMarshalByReference(IEnumerable<int> indexValues, ScriptArraySetInt32Callback setArrayElementCallback, int baseIndex)
        {
            _setCallbackInt32 = setArrayElementCallback;
            _list = new List<IScriptObject>();
            _isReadable = true;
            _isWriteable = setArrayElementCallback != null;
            _allowSettingOutOfRangeIndexes = false;

            int i = baseIndex;
            foreach (int value in indexValues)
                this.SetItemInternal(i++, new ScriptNumber(value));
        }
        /// <summary>
        /// Initializes a new instance of the ScriptArray class that contains ScriptNumber elements copied from the specified System.Int32 collection.
        /// </summary>
        /// <param name="indexValues">The collection whose elements are copied to the new ScriptArray. Each System.Int32 will be converted to a ScriptNumber.</param>
        /// <param name="setArrayElementCallback">A ScriptArraySetScriptNumberCallback delegate that contains a method to be called when an array element is set. Calling the SetItemInternal method will not invoke the callback.</param>
        /// <param name="baseIndex">The index of the first element in the resulting ScriptArray. This is usually 0 or 1.</param>
        public ScriptArrayMarshalByReference(IEnumerable<int> indexValues, ScriptArraySetScriptNumberCallback setArrayElementCallback, int baseIndex)
        {
            _setCallbackScriptNumber = setArrayElementCallback;
            _list = new List<IScriptObject>();
            _isReadable = true;
            _isWriteable = setArrayElementCallback != null;
            _allowSettingOutOfRangeIndexes = false;

            int i = baseIndex;
            foreach (int value in indexValues)
                this.SetItemInternal(i++, new ScriptNumber(value));
        }

        /// <summary>
        /// Initializes a new instance of the ScriptArray class that contains ScriptString elements copied from the specified System.String collection.
        /// </summary>
        /// <param name="indexValues">The collection whose elements are copied to the new ScriptArray. Each System.String will be converted to a ScriptString.</param>
        /// <param name="baseIndex">The index of the first element in the resulting ScriptArray. This is usually 0 or 1.</param>
        public ScriptArrayMarshalByReference(IEnumerable<string> indexValues, int baseIndex)
        {
            _list = new List<IScriptObject>();
            _isReadable = true;
            _isWriteable = false;
            _allowSettingOutOfRangeIndexes = false;

            int i = baseIndex;
            foreach (string value in indexValues)
                this.SetItemInternal(i++, new ScriptString(value));
        }
        /// <summary>
        /// Initializes a new instance of the ScriptArray class that contains ScriptString elements copied from the specified System.String collection.
        /// </summary>
        /// <param name="indexValues">The collection whose elements are copied to the new ScriptArray. Each System.String will be converted to a ScriptString.</param>
        /// <param name="setArrayElementCallback">A ScriptArraySetStringCallback delegate that contains a method to be called when an array element is set. Calling the SetItemInternal method will not invoke the callback.</param>
        /// <param name="baseIndex">The index of the first element in the resulting ScriptArray. This is usually 0 or 1.</param>
        public ScriptArrayMarshalByReference(IEnumerable<string> indexValues, ScriptArraySetStringCallback setArrayElementCallback, int baseIndex)
        {
            _setCallbackString = setArrayElementCallback;
            _list = new List<IScriptObject>();
            _isReadable = true;
            _isWriteable = setArrayElementCallback != null;
            _allowSettingOutOfRangeIndexes = false;

            int i = baseIndex;
            foreach (string value in indexValues)
                this.SetItemInternal(i++, new ScriptString(value));
        }
        /// <summary>
        /// Initializes a new instance of the ScriptArray class that contains ScriptString elements copied from the specified System.String collection.
        /// </summary>
        /// <param name="indexValues">The collection whose elements are copied to the new ScriptArray. Each System.String will be converted to a ScriptString.</param>
        /// <param name="setArrayElementCallback">A ScriptArraySetScriptStringCallback delegate that contains a method to be called when an array element is set. Calling the SetItemInternal method will not invoke the callback.</param>
        /// <param name="baseIndex">The index of the first element in the resulting ScriptArray. This is usually 0 or 1.</param>
        public ScriptArrayMarshalByReference(IEnumerable<string> indexValues, ScriptArraySetScriptStringCallback setArrayElementCallback, int baseIndex)
        {
            _setCallbackScriptString = setArrayElementCallback;
            _list = new List<IScriptObject>();
            _isReadable = true;
            _isWriteable = setArrayElementCallback != null;
            _allowSettingOutOfRangeIndexes = false;

            int i = baseIndex;
            foreach (string value in indexValues)
                this.SetItemInternal(i++, new ScriptString(value));
        }

        /// <summary>
        /// Initializes a new instance of the ScriptArray class that contains ScriptBoolean elements copied from the specified System.Boolean collection.
        /// </summary>
        /// <param name="indexValues">The collection whose elements are copied to the new ScriptArray. Each System.Boolean will be converted to a ScriptBoolean.</param>
        /// <param name="baseIndex">The index of the first element in the resulting ScriptArray. This is usually 0 or 1.</param>
        public ScriptArrayMarshalByReference(IEnumerable<bool> indexValues, int baseIndex)
        {
            _list = new List<IScriptObject>();
            _isReadable = true;
            _isWriteable = false;
            _allowSettingOutOfRangeIndexes = false;

            int i = baseIndex;
            foreach (bool value in indexValues)
                this.SetItemInternal(i++, new ScriptBoolean(value));
        }
        /// <summary>
        /// Initializes a new instance of the ScriptArray class that contains ScriptBoolean elements copied from the specified System.Boolean collection.
        /// </summary>
        /// <param name="indexValues">The collection whose elements are copied to the new ScriptArray. Each System.Boolean will be converted to a ScriptBoolean.</param>
        /// <param name="setArrayElementCallback">A ScriptArraySetBooleanCallback delegate that contains a method to be called when an array element is set. Calling the SetItemInternal method will not invoke the callback.</param>
        /// <param name="baseIndex">The index of the first element in the resulting ScriptArray. This is usually 0 or 1.</param>
        public ScriptArrayMarshalByReference(IEnumerable<bool> indexValues, ScriptArraySetBooleanCallback setArrayElementCallback, int baseIndex)
        {
            _setCallbackBoolean = setArrayElementCallback;
            _list = new List<IScriptObject>();
            _isReadable = true;
            _isWriteable = setArrayElementCallback != null;
            _allowSettingOutOfRangeIndexes = false;

            int i = baseIndex;
            foreach (bool value in indexValues)
                this.SetItemInternal(i++, new ScriptBoolean(value));
        }
        /// <summary>
        /// Initializes a new instance of the ScriptArray class that contains ScriptBoolean elements copied from the specified System.Boolean collection.
        /// </summary>
        /// <param name="indexValues">The collection whose elements are copied to the new ScriptArray. Each System.Boolean will be converted to a ScriptBoolean.</param>
        /// <param name="setArrayElementCallback">A ScriptArraySetScriptBooleanCallback delegate that contains a method to be called when an array element is set. Calling the SetItemInternal method will not invoke the callback.</param>
        /// <param name="baseIndex">The index of the first element in the resulting ScriptArray. This is usually 0 or 1.</param>
        public ScriptArrayMarshalByReference(IEnumerable<bool> indexValues, ScriptArraySetScriptBooleanCallback setArrayElementCallback, int baseIndex)
        {
            _setCallbackScriptBoolean = setArrayElementCallback;
            _list = new List<IScriptObject>();
            _isReadable = true;
            _isWriteable = setArrayElementCallback != null;
            _allowSettingOutOfRangeIndexes = false;

            int i = baseIndex;
            foreach (bool value in indexValues)
                this.SetItemInternal(i++, new ScriptBoolean(value));
        }

        /// <summary>
        /// Initializes a new instance of the ScriptArray class that contains ScriptDateTime elements copied from the specified System.DateTime collection.
        /// </summary>
        /// <param name="indexValues">The collection whose elements are copied to the new ScriptArray. Each System.DateTime will be converted to a ScriptBoolean.</param>
        /// <param name="baseIndex">The index of the first element in the resulting ScriptArray. This is usually 0 or 1.</param>
        public ScriptArrayMarshalByReference(IEnumerable<DateTime> indexValues, int baseIndex)
        {
            _list = new List<IScriptObject>();
            _isReadable = true;
            _isWriteable = false;
            _allowSettingOutOfRangeIndexes = false;

            int i = baseIndex;
            foreach (DateTime value in indexValues)
                this.SetItemInternal(i++, new ScriptDateTime(value));
        }
        /// <summary>
        /// Initializes a new instance of the ScriptArray class that contains ScriptDateTime elements copied from the specified System.DateTime collection.
        /// </summary>
        /// <param name="indexValues">The collection whose elements are copied to the new ScriptArray. Each System.Boolean will be converted to a ScriptDateTime.</param>
        /// <param name="setArrayElementCallback">A ScriptArraySetDateTimeCallback delegate that contains a method to be called when an array element is set. Calling the SetItemInternal method will not invoke the callback.</param>
        /// <param name="baseIndex">The index of the first element in the resulting ScriptArray. This is usually 0 or 1.</param>
        public ScriptArrayMarshalByReference(IEnumerable<DateTime> indexValues, ScriptArraySetDateTimeCallback setArrayElementCallback, int baseIndex)
        {
            _setCallbackDateTime = setArrayElementCallback;
            _list = new List<IScriptObject>();
            _isReadable = true;
            _isWriteable = setArrayElementCallback != null;
            _allowSettingOutOfRangeIndexes = false;

            int i = baseIndex;
            foreach (DateTime value in indexValues)
                this.SetItemInternal(i++, new ScriptDateTime(value));
        }
        /// <summary>
        /// Initializes a new instance of the ScriptArray class that contains ScriptDateTime elements copied from the specified System.DateTime collection.
        /// </summary>
        /// <param name="indexValues">The collection whose elements are copied to the new ScriptArray. Each System.Boolean will be converted to a ScriptDateTime.</param>
        /// <param name="setArrayElementCallback">A ScriptArraySetScriptDateTimeCallback delegate that contains a method to be called when an array element is set. Calling the SetItemInternal method will not invoke the callback.</param>
        /// <param name="baseIndex">The index of the first element in the resulting ScriptArray. This is usually 0 or 1.</param>
        public ScriptArrayMarshalByReference(IEnumerable<DateTime> indexValues, ScriptArraySetScriptDateTimeCallback setArrayElementCallback, int baseIndex)
        {
            _setCallbackScriptDateTime = setArrayElementCallback;
            _list = new List<IScriptObject>();
            _isReadable = true;
            _isWriteable = setArrayElementCallback != null;
            _allowSettingOutOfRangeIndexes = false;

            int i = baseIndex;
            foreach (DateTime value in indexValues)
                this.SetItemInternal(i++, new ScriptDateTime(value));
        }

        /// <summary>
        /// Initializes a new instance of the ScriptArray class that contains ScriptTimeSpan elements copied from the specified System.TimeSpan collection.
        /// </summary>
        /// <param name="indexValues">The collection whose elements are copied to the new ScriptArray. Each System.TimeSpan will be converted to a ScriptBoolean.</param>
        /// <param name="baseIndex">The index of the first element in the resulting ScriptArray. This is usually 0 or 1.</param>
        public ScriptArrayMarshalByReference(IEnumerable<TimeSpan> indexValues, int baseIndex)
        {
            _list = new List<IScriptObject>();
            _isReadable = true;
            _isWriteable = false;
            _allowSettingOutOfRangeIndexes = false;

            int i = baseIndex;
            foreach (TimeSpan value in indexValues)
                this.SetItemInternal(i++, new ScriptTimeSpan(value));
        }
        /// <summary>
        /// Initializes a new instance of the ScriptArray class that contains ScriptTimeSpan elements copied from the specified System.TimeSpan collection.
        /// </summary>
        /// <param name="indexValues">The collection whose elements are copied to the new ScriptArray. Each System.Boolean will be converted to a ScriptTimeSpan.</param>
        /// <param name="setArrayElementCallback">A ScriptArraySetTimeSpanCallback delegate that contains a method to be called when an array element is set. Calling the SetItemInternal method will not invoke the callback.</param>
        /// <param name="baseIndex">The index of the first element in the resulting ScriptArray. This is usually 0 or 1.</param>
        public ScriptArrayMarshalByReference(IEnumerable<TimeSpan> indexValues, ScriptArraySetTimeSpanCallback setArrayElementCallback, int baseIndex)
        {
            _setCallbackTimeSpan = setArrayElementCallback;
            _list = new List<IScriptObject>();
            _isReadable = true;
            _isWriteable = setArrayElementCallback != null;
            _allowSettingOutOfRangeIndexes = false;

            int i = baseIndex;
            foreach (TimeSpan value in indexValues)
                this.SetItemInternal(i++, new ScriptTimeSpan(value));
        }
        /// <summary>
        /// Initializes a new instance of the ScriptArray class that contains ScriptTimeSpan elements copied from the specified System.TimeSpan collection.
        /// </summary>
        /// <param name="indexValues">The collection whose elements are copied to the new ScriptArray. Each System.Boolean will be converted to a ScriptTimeSpan.</param>
        /// <param name="setArrayElementCallback">A ScriptArraySetScriptTimeSpanCallback delegate that contains a method to be called when an array element is set. Calling the SetItemInternal method will not invoke the callback.</param>
        /// <param name="baseIndex">The index of the first element in the resulting ScriptArray. This is usually 0 or 1.</param>
        public ScriptArrayMarshalByReference(IEnumerable<TimeSpan> indexValues, ScriptArraySetScriptTimeSpanCallback setArrayElementCallback, int baseIndex)
        {
            _setCallbackScriptTimeSpan = setArrayElementCallback;
            _list = new List<IScriptObject>();
            _isReadable = true;
            _isWriteable = setArrayElementCallback != null;
            _allowSettingOutOfRangeIndexes = false;

            int i = baseIndex;
            foreach (TimeSpan value in indexValues)
                this.SetItemInternal(i++, new ScriptTimeSpan(value));
        }

        /// <summary>
        /// Initializes a new instance of the ScriptArray class that contains elements copied from the specified collection.
        /// </summary>
        /// <param name="indexValues">The collection whose elements are copied to the new ScriptArray.</param>
        /// <param name="baseIndex">The index of the first element in the resulting ScriptArray. This is usually 0 or 1.</param>
        public ScriptArrayMarshalByReference(IEnumerable<IScriptObject> indexValues, int baseIndex)
            : this(indexValues, null, baseIndex)
        { }
        /// <summary>
        /// Initializes a new instance of the ScriptArray class that contains elements copied from the specified collection.
        /// </summary>
        /// <param name="indexValues">The collection whose elements are copied to the new ScriptArray.</param>
        /// <param name="setArrayElementCallback">A ScriptArraySetScriptObjectCallback delegate that contains a method to be called when an array element is set. Calling the SetItemInternal method will not invoke the callback.</param>
        /// <param name="baseIndex">The index of the first element in the resulting ScriptArray. This is usually 0 or 1.</param>
        public ScriptArrayMarshalByReference(IEnumerable<IScriptObject> indexValues, ScriptArraySetScriptObjectCallback setArrayElementCallback, int baseIndex)
        {
            _setCallbackScriptObject = setArrayElementCallback;
            _list = new List<IScriptObject>();
            _isReadable = true;
            _isWriteable = setArrayElementCallback != null;
            _allowSettingOutOfRangeIndexes = false;

            int i = baseIndex;
            foreach (IScriptObject value in indexValues)
                this.SetItemInternal(i++, value);
        }

        /// <summary>
        /// Gets a value indicating whether the ScriptArray elements can be read.
        /// </summary>
        [ScriptObjectPropertyAttribute("Can Read", "Gets a value indicating whether the ScriptArray elements can be read.")]
        public ScriptBoolean CanRead
        {
            get
            {
                return new ScriptBoolean(_isReadable);
            }
        }

        /// <summary>
        /// Gets a value indicating whether the ScriptArray elements can be written to.
        /// </summary>
        [ScriptObjectPropertyAttribute("Can Write", "Gets a value indicating whether the ScriptArray elements can be written to.")]
        public ScriptBoolean CanWrite
        {
            get
            {
                return new ScriptBoolean(_isWriteable);
            }
        }

        /// <summary>
        /// Sets a value to the element at the specified position in the ScriptArray. The ScriptArray will automatically expand (and be filled with null elements) if the specified index is out of bounds. This method will not invoke the set callback which may have been applied to the ScriptArray.
        /// </summary>
        /// <param name="index">The position of the ScriptArray element to set.</param>
        /// <param name="o">The new value for the specified element.</param>
        public virtual void SetItemInternal(int index, IScriptObject o)
        {
            // This is used to set the array items internally since the array might be marcked as read only.
            ensureCapacity(index); // prevent blow out by ensuring the list is big enough.
            _list[index] = o;

            if (_lowestIndex == -1 || index < _lowestIndex)
                _lowestIndex = index;

            if (_highestIndex == -1 || index > _highestIndex)
                _highestIndex = index;
        }

        /// <summary>
        /// Gets the value at the specified position in the ScriptArray.
        /// </summary>
        /// <param name="index">The position of the ScriptArray element to get.</param>
        /// <returns>The value at the specified position in the ScriptArray.</returns>
        [ScriptObjectPropertyAttribute("Indexer", "Array element")]
        public virtual IScriptObject this[int index]
        {
            get
            {
                if (!_isReadable)
                    throw new Exception("The array elements are write only.");

                if (index < _list.Count)
                    return _list[index];
                else
                    return null;
            }
            set
            {
                if (!_isWriteable)
                    throw new Exception("The array elements are read only.");

                if (_allowSettingOutOfRangeIndexes == false)
                    if (index < _lowestIndex || index > _highestIndex)
                        throw new IndexOutOfRangeException("The specified index of " + index + " is outside the range of " + _lowestIndex + " to " + _highestIndex + ".");

                SetItemInternal(index, value);

                if (_setCallbackDouble != null)
                    _setCallbackDouble(index, value.ToPrimitiveDouble());
                else if (_setCallbackInt32 != null)
                    _setCallbackInt32(index, value.ToPrimitiveInt32());
                else if (_setCallbackScriptNumber != null)
                    _setCallbackScriptNumber(new ScriptNumber(index), (ScriptNumber)value);

                else if (_setCallbackString != null)
                    _setCallbackString(index, value.ToPrimitiveString());
                else if (_setCallbackScriptString != null)
                    _setCallbackScriptString(new ScriptNumber(index), (ScriptString)value);

                else if (_setCallbackBoolean != null)
                    _setCallbackBoolean(index, value.ToPrimitiveBoolean());
                else if (_setCallbackScriptBoolean != null)
                    _setCallbackScriptBoolean(new ScriptNumber(index), (ScriptBoolean)value);

                else if (_setCallbackDateTime != null)
                    _setCallbackDateTime(index, (DateTime)value.ToPrimitiveObject());
                else if (_setCallbackScriptDateTime != null)
                    _setCallbackScriptDateTime(new ScriptNumber(index), (ScriptDateTime)value);

                else if (_setCallbackTimeSpan != null)
                    _setCallbackTimeSpan(index, (TimeSpan)value.ToPrimitiveObject());
                else if (_setCallbackScriptTimeSpan != null)
                    _setCallbackScriptTimeSpan(new ScriptNumber(index), (ScriptTimeSpan)value);

                else if (_setCallbackScriptObject != null)
                    _setCallbackScriptObject(index, value);
            }
        }

        /// <summary>
        /// Gets the total length of the array from index 0 to the last element.
        /// </summary>
        [ScriptObjectPropertyAttribute("Length", "Gets the total length of the array from index 0 to the last element.")]
        public virtual ScriptNumber Length
        {
            get { return new ScriptNumber(this.PrimitiveLength); }
        }

        /// <summary>
        /// Gets the number of items from the lowest index to the highest index in the array. Empty indexes are included.
        /// </summary>
        [ScriptObjectPropertyAttribute("BoundsLength", "Gets the number of items from the lowest index to the highest index in the array. Empty indexes are included.")]
        public virtual ScriptNumber BoundsLength
        {
            get { return new ScriptNumber(this.PrimitiveBoundsLength); }
        }

        /// <summary>
        /// Gets the lowest index that has been assigned a value.  Returns -1 if no array elements have been set.
        /// </summary>
        [ScriptObjectProperty("Lowest Index", "Gets the lowest index that has been assigned a value.  Returns -1 if no array elements have been set.")]
        public virtual ScriptNumber LowestIndex
        {
            get { return new ScriptNumber(_lowestIndex); }
        }

        /// <summary>
        /// Gets the highest index that has been assigned a value.  Returns -1 if no array elements have been set.
        /// </summary>
        [ScriptObjectProperty("Highest Index", "Gets the highest index that has been assigned a value.  Returns -1 if no array elements have been set.")]
        public virtual ScriptNumber HighestIndex
        {
            get { return new ScriptNumber(_highestIndex); }
        }

        /// <summary>
        /// Returns the total number of elements in the array from 0 to the last element.
        /// </summary>
        public virtual int PrimitiveLength
        {
            get { return _list.Count; }
        }

        /// <summary>
        /// Returns the number of elements from the lowest set index to the highest set index.
        /// </summary>
        public virtual int PrimitiveBoundsLength
        {
            get
            {
                if (_lowestIndex == -1 && _highestIndex == -1)
                    return 0;
                else
                    return _highestIndex - _lowestIndex + 1;
            }
        }

        /// <summary>
        /// Gets the lowest index that has been assigned a value.  Returns -1 if no array elements have been set.
        /// </summary>
        public virtual int PrimitiveLowestIndex
        {
            get { return _lowestIndex; }
        }

        /// <summary>
        /// Gets the highest index that has been assigned a value.  Returns -1 if no array elements have been set.
        /// </summary>
        public virtual int PrimitiveHighestIndex
        {
            get { return _highestIndex; }
        }

        private void ensureCapacity(int count)
        {
            while (_list.Count <= count)
                _list.Add(null);
        }

        [ScriptObjectMethodAttribute("Add", "Adds an item to the array. If the array is empty then the item will be added at index zero. Returns the position into which the new element was inserted.")]
        [ScriptObjectMethodParameter("Value", "The item to add to the array.")]
        public ScriptNumber Add(IScriptObject value)
        {
            int index = _highestIndex + 1;
            SetItemInternal(index, value);
            return new ScriptNumber(index);
        }

        [ScriptObjectMethodAttribute("Clear", "Removes all items from the array.")]
        public void Clear()
        {
            _list.Clear();
            _lowestIndex = -1;
            _highestIndex = -1;
        }

        [ScriptObjectMethodAttribute("Contains", "Determines whether the array contains a specific value.")]
        [ScriptObjectMethodParameter("Value", "The item to locate in the array.")]
        public ScriptBoolean Contains(IScriptObject value)
        {
            //return new ScriptBoolean(_list.Contains(value)); <-- this does not work
            object val = value.ToPrimitiveObject();

            for (int i = _lowestIndex; i <= _highestIndex; i++)
            {
                object o = _list[i].ToPrimitiveObject();
                if (object.Equals(o, val))
                    return ScriptBoolean.True;
            }

            return ScriptBoolean.False;
        }

        [ScriptObjectMethodAttribute("IndexOf", "Determines the index of a specific item in the array. Returns the index of value if found in the list; otherwise, -1.")]
        [ScriptObjectMethodParameter("Value", "The item to locate in the array.")]
        public ScriptNumber IndexOf(IScriptObject value)
        {
            //return new ScriptNumber(_list.IndexOf(value)); <-- this does not work
            object val = value.ToPrimitiveObject();

            for (int i = _lowestIndex; i <= _highestIndex; i++)
            {
                object o = _list[i].ToPrimitiveObject();
                if (object.Equals(o, val))
                    return new ScriptNumber(i);
            }

            return new ScriptNumber(-1);
        }

        /// <summary>
        /// Inserts an item to the array at the specified index.
        /// </summary>
        /// <param name="index">The zero-based index at which value should be inserted.</param>
        /// <param name="value">The item to insert into the array.</param>
        public void Insert(int index, IScriptObject value)
        {
            ensureCapacity(index); // prevent blow out by ensuring the list is big enough.
            _list.Insert(index, value);

            if (_lowestIndex == -1 || index < _lowestIndex)
                _lowestIndex = index;

            if (_highestIndex == -1 || index > _highestIndex)
                _highestIndex = index;
            else if (index < _highestIndex)
                _highestIndex++; // since we inserted the highest index was bumped up by one.
        }

        [ScriptObjectMethodAttribute("Insert", "Inserts an item to the array at the specified index.")]
        [ScriptObjectMethodParameter("Index", "The zero-based index at which value should be inserted.")]
        [ScriptObjectMethodParameter("Value", "The item to insert into the array.")]
        public void Insert(ScriptNumber index, IScriptObject value)
        {
            Insert((int)index, value);
        }

        [ScriptObjectMethodAttribute("Remove", "Removes the first occurrence of a specific object from the array.")]
        [ScriptObjectMethodParameter("Value", "The object to remove from the array.")]
        public void Remove(IScriptObject value)
        {
            _list.Remove(value);
            if (_list.Count == 0)
            {
                _highestIndex = -1;
                _lowestIndex = -1;
            }
            else
                _highestIndex--;
        }

        /// <summary>
        /// Removes the item at the specified index from the array.
        /// </summary>
        /// <param name="index">The zero-based index of the item to remove.</param>
        public void RemoveAt(int index)
        {
            _list.RemoveAt(index);
            if (_list.Count == 0)
            {
                _highestIndex = -1;
                _lowestIndex = -1;
            }
            else
                _highestIndex--;
        }

        [ScriptObjectMethodAttribute("Remove At", "Removes the item at the specified index from the array.")]
        [ScriptObjectMethodParameter("Index", "The zero-based index of the item to remove.")]
        public void RemoveAt(ScriptNumber index)
        {
            RemoveAt((int)index);
        }

        public override string ToString()  // so we can see the value in the debugger
        {
            return "Length = " + _list.Count.ToString();
        }

        public override string ToPrimitiveString()
        {
            if (this.PrimitiveLength == 0) // prevent errors when there are no items (and min and max are -1).
                return "";

            // Output a comma delimited list similar to what javascript does (but only include elements from the lowest to highest index).
            string s = "";
            for (int i = _lowestIndex; i <= _highestIndex; i++)
            {
                if (_list[i] != null)
                    s += _list[i].ToPrimitiveString();

                if (i != _highestIndex)
                    s += ",";
            }

            return s;
        }

        public override string ToDisplayString(ScriptObjectPropertyAttribute attribute)
        {
            if (this.PrimitiveLength == 0) // prevent errors when there are no items (and min and max are -1).
                return "";

            // Output a comma delimited list similar to what javascript does (but only include elements from the lowest to highest index).
            string s = "";
            for (int i = _lowestIndex; i <= _highestIndex; i++)
            {
                if (_list[i] != null)
                    s += _list[i].ToDisplayString(attribute);

                if (i != _highestIndex)
                    s += ",";
            }

            return s;
        }

        public IScriptArray ToBaseScriptArray()
        {
            // If this is already a base ScriptArray type then just return it.
            if (this.GetType() == typeof(ScriptArrayMarshalByReference))
                return this;

            // Copy all the elements from the array wrapper into a base ScriptArray object.
            ScriptArrayMarshalByReference newBaseArray = new ScriptArrayMarshalByReference(_isReadable, _isWriteable);

            if (this.PrimitiveLength == 0) // prevent errors when there are no items (and min and max are -1).
                return newBaseArray;

            for (int i = _lowestIndex; i <= _highestIndex; i++)
                newBaseArray.SetItemInternal(i, _list[i]);

            return newBaseArray;
        }


        public override double ToPrimitiveDouble()
        {
            return 0; // returns 0 like javascript
        }

        public override Int32 ToPrimitiveInt32()
        {
            return 0; // returns 0 like javascript
        }

        public override bool ToPrimitiveBoolean()
        {
            return true; // returns true like javascript
        }
        
        [Obsolete("Use the Serialize instance method and Deserialize static methods instead since they are faster.", false)]
        public virtual object[] ToPrimitiveArray()
        {
            object[] primArray = new object[_highestIndex + 1];

            if (this.PrimitiveLength == 0) // prevent errors when there are no items (and min and max are -1).
                return primArray;

            for (int i = _lowestIndex; i <= _highestIndex; i++)
            {
                if (_list[i] != null)
                    primArray[i] = _list[i].ToPrimitiveObject();
                else
                    primArray[i] = null;
            }

            return primArray;
        }

        public virtual byte[] SerializeAsBaseArray()
        {
            IScriptArray a = ToBaseScriptArray();
            return a.Serialize();
        }

        #region ISerializable Members
        protected ScriptArrayMarshalByReference(SerializationInfo info, StreamingContext context)
        { 
            _isReadable = info.GetBoolean("r");
            _isWriteable = info.GetBoolean("w");
            _highestIndex = info.GetInt32("hi");
            _lowestIndex = info.GetInt32("lo");
            _list = (List<IScriptObject>)info.GetValue("l", typeof(List<IScriptObject>));
        }
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            //info.AddValue("o", this, this.GetType());

            info.AddValue("r", _isReadable);
            info.AddValue("w", _isWriteable);
            info.AddValue("hi", _highestIndex);
            info.AddValue("lo", _lowestIndex);
            info.AddValue("l", _list, typeof(List<IScriptObject>));
        }
        #endregion
    }

    [Serializable]
    [ScriptObjectClass("Array", "The Array object is used to store a set of values in a single variable name. Arrays are zero indexed meaning the first element is at index zero. Elements in arrays can be different types. The array elements are accessed through computed indexes. Square brackets [...] are used in the script to enclose the index number. " +
        "Use the ScriptArrayMarshalByValue for class immutable arrays or arrays which do not need to send a set index value back to the driver. For example a read only list of zone names. This gives the best performance (10 times faster tan ScriptArrayMarshalByReference) but index value changes are not detected by the source driver."
        )]
    public class ScriptArrayMarshalByValue : ScriptObject, IScriptArray, ISerializable
    {
        protected List<IScriptObject> _list;
        protected bool _isReadable;
        protected bool _isWriteable;

        protected int _lowestIndex = -1; // the lowest element index that has been assigned a value.
        protected int _highestIndex = -1; // the highest element index that has been assigned a value.

        protected bool _allowSettingOutOfRangeIndexes;

        /// <summary>
        /// Initializes a new empty readable and writeable instance of the ScriptArray class. Use the SetItemInternal method to populate.
        /// </summary>
        [ScriptObjectConstructor("Array", "Initializes a new instance of the Array class.")]
        public ScriptArrayMarshalByValue()
            : this(null, true, true)
        {
        }

        /// <summary>
        /// Initializes a new empty instance of the ScriptArray class with the specified read/write accessibility. Use the SetItemInternal method to populate.
        /// </summary>
        /// <param name="isReadable">Indicates if the ScriptArray elements can be read.</param>
        /// <param name="isWriteable">Indicates if the ScriptArray elements can be written to.</param>
        public ScriptArrayMarshalByValue(bool isReadable, bool isWriteable)
            : this(null, isReadable, isWriteable)
        {
        }

        /// <summary>
        /// Initializes a new instance of the ScriptArray class populated with the given ScriptObjects starting at index 0.
        /// </summary>
        /// <param name="items"></param>
        /// <param name="isReadable">Indicates if the ScriptArray elements can be read.</param>
        /// <param name="isWriteable">Indicates if the ScriptArray elements can be written to.</param>
        public ScriptArrayMarshalByValue(IEnumerable<IScriptObject> items, bool isReadable, bool isWriteable)
        {
            if (items != null)
            {
                _list = new List<IScriptObject>(items);
                _lowestIndex = 0;
                _highestIndex = _list.Count - 1;
            }
            else
                _list = new List<IScriptObject>();
            
            _isReadable = isReadable;
            _isWriteable = isWriteable;
            _allowSettingOutOfRangeIndexes = true;
        }

        /// <summary>
        /// Initializes a new instance of the ScriptArray class that contains ScriptNumber elements copied from the specified System.Double collection.
        /// </summary>
        /// <param name="indexValues">The collection whose elements are copied to the new ScriptArray. Each System.Double will be converted to a ScriptNumber.</param>
        /// <param name="baseIndex">The index of the first element in the resulting ScriptArray. This is usually 0 or 1.</param>
        public ScriptArrayMarshalByValue(IEnumerable<double> indexValues, int baseIndex)
            : this(indexValues, baseIndex, true)
        {
        }
        /// <summary>
        /// Initializes a new instance of the ScriptArray class that contains ScriptNumber elements copied from the specified System.Double collection.
        /// </summary>
        /// <param name="indexValues">The collection whose elements are copied to the new ScriptArray. Each System.Double will be converted to a ScriptNumber.</param>
        /// <param name="baseIndex">The index of the first element in the resulting ScriptArray. This is usually 0 or 1.</param>
        /// <param name="isWritable">Indicates if the array object is writable.</param>
        public ScriptArrayMarshalByValue(IEnumerable<double> indexValues, int baseIndex, bool isWritable)
        {
            _list = new List<IScriptObject>();
            _isReadable = true;
            _isWriteable = isWritable;
            _allowSettingOutOfRangeIndexes = false;

            int i = baseIndex;
            foreach (double value in indexValues)
                this.SetItemInternal(i++, new ScriptNumber(value));
        }


        /// <summary>
        /// Initializes a new instance of the ScriptArray class that contains ScriptNumber elements copied from the specified System.Int32 collection.
        /// </summary>
        /// <param name="indexValues">The collection whose elements are copied to the new ScriptArray. Each System.Int32 will be converted to a ScriptNumber.</param>
        /// <param name="baseIndex">The index of the first element in the resulting ScriptArray. This is usually 0 or 1.</param>
        public ScriptArrayMarshalByValue(IEnumerable<int> indexValues, int baseIndex)
            : this(indexValues, baseIndex, true)
        {
        }
        /// <summary>
        /// Initializes a new instance of the ScriptArray class that contains ScriptNumber elements copied from the specified System.Int32 collection.
        /// </summary>
        /// <param name="indexValues">The collection whose elements are copied to the new ScriptArray. Each System.Int32 will be converted to a ScriptNumber.</param>
        /// <param name="baseIndex">The index of the first element in the resulting ScriptArray. This is usually 0 or 1.</param>
        /// <param name="isWritable">Indicates if the array object is writable.</param>
        public ScriptArrayMarshalByValue(IEnumerable<int> indexValues, int baseIndex, bool isWritable)
        {
            _list = new List<IScriptObject>();
            _isReadable = true;
            _isWriteable = isWritable;
            _allowSettingOutOfRangeIndexes = false;

            int i = baseIndex;
            foreach (int value in indexValues)
                this.SetItemInternal(i++, new ScriptNumber(value));
        }

        /// <summary>
        /// Initializes a new instance of the ScriptArray class that contains ScriptString elements copied from the specified System.String collection.
        /// </summary>
        /// <param name="indexValues">The collection whose elements are copied to the new ScriptArray. Each System.String will be converted to a ScriptString.</param>
        /// <param name="baseIndex">The index of the first element in the resulting ScriptArray. This is usually 0 or 1.</param>
        public ScriptArrayMarshalByValue(IEnumerable<string> indexValues, int baseIndex)
            : this(indexValues, baseIndex, true)
        {
        }
        /// <summary>
        /// Initializes a new instance of the ScriptArray class that contains ScriptString elements copied from the specified System.String collection.
        /// </summary>
        /// <param name="indexValues">The collection whose elements are copied to the new ScriptArray. Each System.String will be converted to a ScriptString.</param>
        /// <param name="baseIndex">The index of the first element in the resulting ScriptArray. This is usually 0 or 1.</param>
        /// <param name="isWritable">Indicates if the array object is writable.</param>
        public ScriptArrayMarshalByValue(IEnumerable<string> indexValues, int baseIndex, bool isWritable)
        {
            _list = new List<IScriptObject>();
            _isReadable = true;
            _isWriteable = isWritable;
            _allowSettingOutOfRangeIndexes = false;

            int i = baseIndex;
            foreach (string value in indexValues)
                this.SetItemInternal(i++, new ScriptString(value));
        }

        /// <summary>
        /// Initializes a new instance of the ScriptArray class that contains ScriptBoolean elements copied from the specified System.Boolean collection.
        /// </summary>
        /// <param name="indexValues">The collection whose elements are copied to the new ScriptArray. Each System.Boolean will be converted to a ScriptBoolean.</param>
        /// <param name="baseIndex">The index of the first element in the resulting ScriptArray. This is usually 0 or 1.</param>
        public ScriptArrayMarshalByValue(IEnumerable<bool> indexValues, int baseIndex)
            : this(indexValues, baseIndex, true)
        {
        }
        /// <summary>
        /// Initializes a new instance of the ScriptArray class that contains ScriptBoolean elements copied from the specified System.Boolean collection.
        /// </summary>
        /// <param name="indexValues">The collection whose elements are copied to the new ScriptArray. Each System.Boolean will be converted to a ScriptBoolean.</param>
        /// <param name="baseIndex">The index of the first element in the resulting ScriptArray. This is usually 0 or 1.</param>
        /// <param name="isWritable">Indicates if the array object is writable.</param>
        public ScriptArrayMarshalByValue(IEnumerable<bool> indexValues, int baseIndex, bool isWritable)
        {
            _list = new List<IScriptObject>();
            _isReadable = true;
            _isWriteable = isWritable;
            _allowSettingOutOfRangeIndexes = false;

            int i = baseIndex;
            foreach (bool value in indexValues)
                this.SetItemInternal(i++, new ScriptBoolean(value));
        }

        /// <summary>
        /// Initializes a new instance of the ScriptArray class that contains ScriptDateTime elements copied from the specified System.DateTime collection.
        /// </summary>
        /// <param name="indexValues">The collection whose elements are copied to the new ScriptArray. Each System.DateTime will be converted to a ScriptBoolean.</param>
        /// <param name="baseIndex">The index of the first element in the resulting ScriptArray. This is usually 0 or 1.</param>
        public ScriptArrayMarshalByValue(IEnumerable<DateTime> indexValues, int baseIndex)
            : this(indexValues, baseIndex, true)
        {
        }
        /// <summary>
        /// Initializes a new instance of the ScriptArray class that contains ScriptDateTime elements copied from the specified System.DateTime collection.
        /// </summary>
        /// <param name="indexValues">The collection whose elements are copied to the new ScriptArray. Each System.DateTime will be converted to a ScriptBoolean.</param>
        /// <param name="baseIndex">The index of the first element in the resulting ScriptArray. This is usually 0 or 1.</param>
        /// <param name="isWritable">Indicates if the array object is writable.</param>
        public ScriptArrayMarshalByValue(IEnumerable<DateTime> indexValues, int baseIndex, bool isWritable)
        {
            _list = new List<IScriptObject>();
            _isReadable = true;
            _isWriteable = isWritable;
            _allowSettingOutOfRangeIndexes = false;

            int i = baseIndex;
            foreach (DateTime value in indexValues)
                this.SetItemInternal(i++, new ScriptDateTime(value));
        }

        /// <summary>
        /// Initializes a new instance of the ScriptArray class that contains ScriptTimeSpan elements copied from the specified System.TimeSpan collection.
        /// </summary>
        /// <param name="indexValues">The collection whose elements are copied to the new ScriptArray. Each System.TimeSpan will be converted to a ScriptBoolean.</param>
        /// <param name="baseIndex">The index of the first element in the resulting ScriptArray. This is usually 0 or 1.</param>
        public ScriptArrayMarshalByValue(IEnumerable<TimeSpan> indexValues, int baseIndex)
            : this(indexValues, baseIndex, true)
        {
        }
        /// <summary>
        /// Initializes a new instance of the ScriptArray class that contains ScriptTimeSpan elements copied from the specified System.TimeSpan collection.
        /// </summary>
        /// <param name="indexValues">The collection whose elements are copied to the new ScriptArray. Each System.TimeSpan will be converted to a ScriptBoolean.</param>
        /// <param name="baseIndex">The index of the first element in the resulting ScriptArray. This is usually 0 or 1.</param>
        /// <param name="isWritable">Indicates if the array object is writable.</param>
        public ScriptArrayMarshalByValue(IEnumerable<TimeSpan> indexValues, int baseIndex, bool isWritable)
        {
            _list = new List<IScriptObject>();
            _isReadable = true;
            _isWriteable = isWritable;
            _allowSettingOutOfRangeIndexes = false;

            int i = baseIndex;
            foreach (TimeSpan value in indexValues)
                this.SetItemInternal(i++, new ScriptTimeSpan(value));
        }

        /// <summary>
        /// Initializes a new instance of the ScriptArray class that contains elements copied from the specified collection.
        /// </summary>
        /// <param name="indexValues">The collection whose elements are copied to the new ScriptArray.</param>
        /// <param name="baseIndex">The index of the first element in the resulting ScriptArray. This is usually 0 or 1.</param>
        public ScriptArrayMarshalByValue(IEnumerable<IScriptObject> indexValues, int baseIndex)
            : this(indexValues, baseIndex, true)
        {
        }
        /// <summary>
        /// Initializes a new instance of the ScriptArray class that contains elements copied from the specified collection.
        /// </summary>
        /// <param name="indexValues">The collection whose elements are copied to the new ScriptArray.</param>
        /// <param name="baseIndex">The index of the first element in the resulting ScriptArray. This is usually 0 or 1.</param>
        /// <param name="isWritable">Indicates if the array object is writable.</param>
        public ScriptArrayMarshalByValue(IEnumerable<IScriptObject> indexValues, int baseIndex, bool isWritable)
        {
            _list = new List<IScriptObject>();
            _isReadable = true;
            _isWriteable = isWritable;
            _allowSettingOutOfRangeIndexes = false;

            int i = baseIndex;
            foreach (IScriptObject value in indexValues)
                this.SetItemInternal(i++, value);
        }

        /// <summary>
        /// Gets a value indicating whether the ScriptArray elements can be read.
        /// </summary>
        [ScriptObjectPropertyAttribute]
        public ScriptBoolean CanRead
        {
            get
            {
                return new ScriptBoolean(_isReadable);
            }
        }

        /// <summary>
        /// Gets a value indicating whether the ScriptArray elements can be written to.
        /// </summary>
        [ScriptObjectPropertyAttribute]
        public ScriptBoolean CanWrite
        {
            get
            {
                return new ScriptBoolean(_isWriteable);
            }
        }

        /// <summary>
        /// Sets a value to the element at the specified position in the ScriptArray. The ScriptArray will automatically expand (and be filled with null elements) if the specified index is out of bounds. This method will not invoke the set callback which may have been applied to the ScriptArray.
        /// </summary>
        /// <param name="index">The position of the ScriptArray element to set.</param>
        /// <param name="o">The new value for the specified element.</param>
        public virtual void SetItemInternal(int index, IScriptObject o)
        {
            // This is used to set the array items internally since the array might be marcked as read only.
            ensureCapacity(index); // prevent blow out by ensuring the list is big enough.
            _list[index] = o;

            if (_lowestIndex == -1 || index < _lowestIndex)
                _lowestIndex = index;

            if (_highestIndex == -1 || index > _highestIndex)
                _highestIndex = index;
        }

        /// <summary>
        /// Gets the value at the specified position in the ScriptArray.
        /// </summary>
        /// <param name="index">The position of the ScriptArray element to get.</param>
        /// <returns>The value at the specified position in the ScriptArray.</returns>
        [ScriptObjectPropertyAttribute("Indexer", "Array element")]
        public virtual IScriptObject this[int index]
        {
            get
            {
                if (!_isReadable)
                    throw new Exception("The array elements are write only.");

                if (index < _list.Count)
                    return _list[index];
                else
                    return null;
            }
            set
            {
                if (!_isWriteable)
                    throw new Exception("The array elements are read only.");

                if (_allowSettingOutOfRangeIndexes == false)
                    if (index < _lowestIndex || index > _highestIndex)
                        throw new IndexOutOfRangeException("The specified index of " + index + " is outside the range of " + _lowestIndex + " to " + _highestIndex + ".");

                SetItemInternal(index, value);
            }
        }

        /// <summary>
        /// Gets the total length of the array from index 0 to the last element.
        /// </summary>
        [ScriptObjectPropertyAttribute("Length", "Gets the total length of the array from index 0 to the last element.")]
        public virtual ScriptNumber Length
        {
            get { return new ScriptNumber(this.PrimitiveLength); }
        }

        /// <summary>
        /// Gets the number of items from the lowest index to the highest index in the array. Empty indexes are included.
        /// </summary>
        [ScriptObjectPropertyAttribute("BoundsLength", "Gets the number of items from the lowest index to the highest index in the array. Empty indexes are included.")]
        public virtual ScriptNumber BoundsLength
        {
            get { return new ScriptNumber(this.PrimitiveBoundsLength); }
        }

        /// <summary>
        /// Gets the lowest index that has been assigned a value.  Returns -1 if no array elements have been set.
        /// </summary>
        [ScriptObjectProperty("Lowest Index", "Gets the lowest index that has been assigned a value.  Returns -1 if no array elements have been set.")]
        public virtual ScriptNumber LowestIndex
        {
            get { return new ScriptNumber(_lowestIndex); }
        }

        /// <summary>
        /// Gets the highest index that has been assigned a value.  Returns -1 if no array elements have been set.
        /// </summary>
        [ScriptObjectProperty("Highest Index", "Gets the highest index that has been assigned a value.  Returns -1 if no array elements have been set.")]
        public virtual ScriptNumber HighestIndex
        {
            get { return new ScriptNumber(_highestIndex); }
        }

        /// <summary>
        /// Returns the total number of elements in the array from 0 to the last element.
        /// </summary>
        public virtual int PrimitiveLength
        {
            get { return _list.Count; }
        }

        /// <summary>
        /// Returns the number of elements from the lowest set index to the highest set index.
        /// </summary>
        public virtual int PrimitiveBoundsLength
        {
            get
            {
                if (_lowestIndex == -1 && _highestIndex == -1)
                    return 0;
                else
                    return _highestIndex - _lowestIndex + 1;
            }
        }

        /// <summary>
        /// Gets the lowest index that has been assigned a value.  Returns -1 if no array elements have been set.
        /// </summary>
        public virtual int PrimitiveLowestIndex
        {
            get { return _lowestIndex; }
        }

        /// <summary>
        /// Gets the highest index that has been assigned a value.  Returns -1 if no array elements have been set.
        /// </summary>
        public virtual int PrimitiveHighestIndex
        {
            get { return _highestIndex; }
        }

        private void ensureCapacity(int count)
        {
            while (_list.Count <= count)
                _list.Add(null);
        }

        [ScriptObjectMethodAttribute("Add", "Adds an item to the array. If the array is empty then the item will be added at index zero. Returns the position into which the new element was inserted.")]
        [ScriptObjectMethodParameter("Value", "The item to add to the array.")]
        public ScriptNumber Add(IScriptObject value)
        {
            int index = _highestIndex + 1;
            SetItemInternal(index, value);
            return new ScriptNumber(index);
        }

        [ScriptObjectMethodAttribute("Clear", "Removes all items from the array.")]
        public void Clear()
        {
            _list.Clear();
            _lowestIndex = -1;
            _highestIndex = -1;
        }

        [ScriptObjectMethodAttribute("Contains", "Determines whether the array contains a specific value.")]
        [ScriptObjectMethodParameter("Value", "The item to locate in the array.")]
        public ScriptBoolean Contains(IScriptObject value)
        {
            //return new ScriptBoolean(_list.Contains(value)); <-- this does not work
            object val = value.ToPrimitiveObject();

            for (int i = _lowestIndex; i <= _highestIndex; i++)
            {
                object o = _list[i].ToPrimitiveObject();
                if (object.Equals(o, val))
                    return ScriptBoolean.True;
            }

            return ScriptBoolean.False;
        }

        [ScriptObjectMethodAttribute("IndexOf", "Determines the index of a specific item in the array. Returns the index of value if found in the list; otherwise, -1.")]
        [ScriptObjectMethodParameter("Value", "The item to locate in the array.")]
        public ScriptNumber IndexOf(IScriptObject value)
        {
            //return new ScriptNumber(_list.IndexOf(value)); <-- this does not work
            object val = value.ToPrimitiveObject();

            for (int i = _lowestIndex; i <= _highestIndex; i++)
            {
                object o = _list[i].ToPrimitiveObject();
                if (object.Equals(o, val))
                    return new ScriptNumber(i);
            }

            return new ScriptNumber(-1);
        }

        /// <summary>
        ///  Inserts an item to the array at the specified index.
        /// </summary>
        /// <param name="index">The zero-based index at which value should be inserted.</param>
        /// <param name="value">The item to insert into the array.</param>
        public void Insert(int index, IScriptObject value)
        {
            ensureCapacity(index - 1); // prevent blow out by ensuring the list is big enough.
            _list.Insert(index, value);

            if (_lowestIndex == -1 || index < _lowestIndex)
                _lowestIndex = index;

            if (_highestIndex == -1 || index > _highestIndex)
                _highestIndex = index;
            else if (index <= _highestIndex)
                _highestIndex++; // since we inserted the highest index was bumped up by one.
        }

        [ScriptObjectMethodAttribute("Insert", "Inserts an item to the array at the specified index.")]
        [ScriptObjectMethodParameter("Index", "The zero-based index at which value should be inserted.")]
        [ScriptObjectMethodParameter("Value", "The item to insert into the array.")]
        public void Insert(ScriptNumber index, IScriptObject value)
        {
            Insert((int)index, value);
        }

        [ScriptObjectMethodAttribute("Remove", "Removes the first occurrence of a specific object from the array.")]
        [ScriptObjectMethodParameter("Value", "The object to remove from the array.")]
        public void Remove(IScriptObject value)
        {
            _list.Remove(value);
            if (_list.Count == 0)
            {
                _highestIndex = -1;
                _lowestIndex = -1;
            }
            else
                _highestIndex--;
        }

        /// <summary>
        /// Removes the item at the specified index from the array.
        /// </summary>
        /// <param name="index">The zero-based index of the item to remove.</param>
        public void RemoveAt(int index)
        {
            _list.RemoveAt(index);
            if (_highestIndex == _lowestIndex) // is there only 1 real item?
            {
                _highestIndex = -1;
                _lowestIndex = -1;
            }
            else
                _highestIndex--;
        }

        [ScriptObjectMethodAttribute("Remove At", "Removes the item at the specified index from the array.")]
        [ScriptObjectMethodParameter("Index", "The zero-based index of the item to remove.")]
        public void RemoveAt(ScriptNumber index)
        {
            RemoveAt((int)index);
        }

        public override string ToString()  // so we can see the value in the debugger
        {
            return "Length = " + _list.Count.ToString();
        }

        public override string ToPrimitiveString()
        {
            if (this.PrimitiveLength == 0) // prevent errors when there are no items (and min and max are -1).
                return "";

            // Output a comma delimited list similar to what javascript does (but only include elements from the lowest to highest index).
            string s = "";
            for (int i = _lowestIndex; i <= _highestIndex; i++)
            {
                if (_list[i] != null)
                    s += _list[i].ToPrimitiveString();

                if (i != _highestIndex)
                    s += ",";
            }

            return s;
        }

        public override string ToDisplayString(ScriptObjectPropertyAttribute attribute)
        {
            if (this.PrimitiveLength == 0) // prevent errors when there are no items (and min and max are -1).
                return "";

            // Output a comma delimited list similar to what javascript does (but only include elements from the lowest to highest index).
            string s = "";
            for (int i = _lowestIndex; i <= _highestIndex; i++)
            {
                if (_list[i] != null)
                    s += _list[i].ToDisplayString(attribute);

                if (i != _highestIndex)
                    s += ",";
            }

            return s;
        }

        public IScriptArray ToBaseScriptArray()
        {
            // If this is already a base ScriptArray type then just return it.
            if (this.GetType() == typeof(ScriptArrayMarshalByValue))
                return this;

            // Copy all the elements from the array wrapper into a base ScriptArray object.
            ScriptArrayMarshalByValue newBaseArray = new ScriptArrayMarshalByValue(_isReadable, _isWriteable);

            if (this.PrimitiveLength == 0) // prevent errors when there are no items (and min and max are -1).
                return newBaseArray;

            for (int i = _lowestIndex; i <= _highestIndex; i++)
                newBaseArray.SetItemInternal(i, _list[i]);

            return newBaseArray;
        }


        public override double ToPrimitiveDouble()
        {
            return 0; // returns 0 like javascript
        }

        public override Int32 ToPrimitiveInt32()
        {
            return 0; // returns 0 like javascript
        }

        public override bool ToPrimitiveBoolean()
        {
            return true; // returns true like javascript
        }
        
        [Obsolete("Use the Serialize instance method and Deserialize static methods instead since they are faster.", false)]
        public virtual object[] ToPrimitiveArray()
        {
            object[] primArray = new object[_highestIndex + 1];

            if (this.PrimitiveLength == 0) // prevent errors when there are no items (and min and max are -1).
                return primArray;

            for (int i = _lowestIndex; i <= _highestIndex; i++)
            {
                if (_list[i] != null)
                    primArray[i] = _list[i].ToPrimitiveObject();
                else
                    primArray[i] = null;
            }

            return primArray;
        }

        public virtual byte[] SerializeAsBaseArray()
        {
            IScriptArray a = ToBaseScriptArray();
            return a.Serialize();
        }

        #region ISerializable Members
        protected ScriptArrayMarshalByValue(SerializationInfo info, StreamingContext context)
        { 
            _isReadable = info.GetBoolean("r");
            _isWriteable = info.GetBoolean("w");
            _highestIndex = info.GetInt32("hi");
            _lowestIndex = info.GetInt32("lo");
            _list = (List<IScriptObject>)info.GetValue("l", typeof(List<IScriptObject>));
        }
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            //info.AddValue("o", this, this.GetType());

            info.AddValue("r", _isReadable);
            info.AddValue("w", _isWriteable);
            info.AddValue("hi", _highestIndex);
            info.AddValue("lo", _lowestIndex);
            info.AddValue("l", _list, typeof(List<IScriptObject>));
        }
        #endregion
    }

    //public class ScriptBooleanArray : ScriptArray
    //{
    //    public override void SetItemInternal(int index, IScriptObject o)
    //    {
    //        if (o is ScriptBoolean == false)
    //            throw new ArgumentException("The value must be boolean.");

    //        base.SetItemInternal(index, o);
    //    }
    //    //public void SetItemInternal(int index, ScriptBoolean o) // todo: this doesn't hide the other one it's just an override
    //    //{
    //    //    base.SetItemInternal(index, o);
    //    //}

    //    public new ScriptBoolean this[int index]
    //    {
    //        get
    //        {
    //            return base[index] as ScriptBoolean;
    //        }
    //    }

    //    /// <summary>
    //    /// Initializes a new empty readable and writeable instance of the ScriptBooleanArray class. Use the SetItemInternal method to populate.
    //    /// </summary>
    //    [ScriptObjectConstructor("Array", "Initializes a new instance of the Array class.")]
    //    public ScriptBooleanArray()
    //        : base(null, true, true)
    //    {
    //    }

    //    /// <summary>
    //    /// Initializes a new empty instance of the ScriptBooleanArray class with the specified read/write accessibility. Use the SetItemInternal method to populate.
    //    /// </summary>
    //    /// <param name="isReadable">Indicates if the ScriptBooleanArray elements can be read.</param>
    //    /// <param name="isWriteable">Indicates if the ScriptBooleanArray elements can be written to.</param>
    //    public ScriptBooleanArray(bool isReadable, bool isWriteable)
    //        : base(null, isReadable, isWriteable)
    //    {
    //    }

    //    /// <summary>
    //    /// Initializes a new instance of the ScriptBooleanArray class that contains ScriptBoolean elements copied from the specified System.Boolean collection.
    //    /// </summary>
    //    /// <param name="indexValues">The collection whose elements are copied to the new ScriptBooleanArray. Each System.Boolean will be converted to a ScriptBoolean.</param>
    //    /// <param name="baseIndex">The index of the first element in the resulting ScriptBooleanArray. This is usually 0 or 1.</param>
    //    public ScriptBooleanArray(IEnumerable<bool> indexValues, int baseIndex)
    //        : this(indexValues, null, baseIndex)
    //    { }
    //    /// <summary>
    //    /// Initializes a new instance of the ScriptBooleanArray class that contains ScriptBoolean elements copied from the specified System.Boolean collection.
    //    /// </summary>
    //    /// <param name="indexValues">The collection whose elements are copied to the new ScriptBooleanArray. Each System.Boolean will be converted to a ScriptBoolean.</param>
    //    /// <param name="setArrayElementCallback">A ScriptArraySetBooleanCallback delegate that contains a method to be called when an array element is set. Calling the SetItemInternal method will not invoke the callback.</param>
    //    /// <param name="baseIndex">The index of the first element in the resulting ScriptBooleanArray. This is usually 0 or 1.</param>
    //    public ScriptBooleanArray(IEnumerable<bool> indexValues, ScriptArraySetBooleanCallback setArrayElementCallback, int baseIndex)
    //    {
    //        _setCallbackBoolean = setArrayElementCallback;
    //        _list = new List<IScriptObject>();
    //        _isReadable = true;
    //        _isWriteable = setArrayElementCallback != null;
    //        _allowSettingOutOfRangeIndexes = false;

    //        int i = baseIndex;
    //        foreach (bool value in indexValues)
    //            this.SetItemInternal(i++, new ScriptBoolean(value));
    //    }
    //}

    [Serializable]
    [ScriptObjectClass("ByteArray", "Represents an array of bytes.")]
    public class ScriptByteArray : ScriptMarshalByRefObject, ISerializable
    {
        byte[] _data;
        string _hash; // hash for the original byte data

        [ScriptObjectConstructor("ScriptByteArray", "Initializes a new instance of the ByteArray class.")]
        public ScriptByteArray(ScriptNumber length)
        {
            _data = new byte[(int)length];
        }

        // for internal use
        public ScriptByteArray(byte[] data)
        {
            if (data == null)
                data = new byte[0];

            if (data != null && data.Length > 0)
            {
                // Now generate a hash code for this file using MD5.
                System.Security.Cryptography.HashAlgorithm alg = System.Security.Cryptography.HashAlgorithm.Create("MD5");
                byte[] fileHashValue = alg.ComputeHash(data);
                _hash = BitConverter.ToString(fileHashValue);
            }
            else
                _hash = "";

            _data = data;
        }

        public byte[] PrimitiveBytes
        {
            get
            {
                return _data;
            }
        }

        public string PrimitiveDataHash
        {
            get
            {
                // Since the image data can be a lot to transfer in remoting, se the image hash to determine if the image needs retreiving (like if it has changed).
                return _hash;
            }
        }

        public override string ToString()
        {
            return "{Data Length=" + _data.Length + " bytes}";
        }

        [ScriptObjectPropertyAttribute("Length", "Gets or sets the length of the byte array.")]
        public ScriptNumber Length
        {
            get { return new ScriptNumber(_data.Length); }
            set
            {
                int v = (int)value;
                if (v < 0)
                    throw new ArgumentOutOfRangeException("length", "The value must be greater than zero.");

                byte[] b = new byte[v];
                Array.Copy(_data, b, Math.Min(v, _data.Length));
                _data = b;
            }
        }

        [ScriptObjectPropertyAttribute("Hash", "Gets the MD5 hash of the byte array.")]
        public ScriptString Hash
        {
            get { return new ScriptString(_hash); }
        }

        [ScriptObjectMethodAttribute("Get Value", "Gets the value at the specified index.")]
        [ScriptObjectMethodParameter("Index", "The index.")]
        public ScriptNumber GetValue(ScriptNumber index)
        {
            return new ScriptNumber((byte)_data.GetValue((int)index));
        }

        [ScriptObjectMethodAttribute("Set Value", "Sets the value at the specified index.")]
        [ScriptObjectMethodParameter("Value", "The value.")]
        [ScriptObjectMethodParameter("Index", "The index.")]
        public void SetValue(ScriptNumber value, ScriptNumber index)
        {
            int v = (int)value;
            if (v < 0 || v > 255)
                throw new ArgumentOutOfRangeException("value", "The value must be from 0 to 255.");

            _data.SetValue((byte)v, (int)index);
        }

        public override object ToPrimitiveObject()
        {
            return (object)_data;
        }

        //public static explicit operator byte[](ScriptByteArray value)
        //{
        //    return value.PrimitiveBytes;
        //}

        public static implicit operator byte[](ScriptByteArray value)
        {
            return value.PrimitiveBytes;
        }

        public override object Clone()
        {
            // This provides better performance than the base Clone().

            byte[] b = new byte[_data.Length];
            System.Buffer.BlockCopy(_data, 0, b, 0, _data.Length);

            return new ScriptByteArray(b);
        }

        #region ISerializable Members
        protected ScriptByteArray(SerializationInfo info, StreamingContext context)
        {
            if (info == null) throw new System.ArgumentNullException("info");
            _data = (byte[])info.GetValue("d", typeof(byte[]));
            _hash = info.GetString("h");
        }

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("d", _data, typeof(byte[]));
            info.AddValue("h", _hash);
        }
        #endregion
    }


    [Serializable]
    [ScriptObjectClass("Image", "Represents an Image.")]
    public class ScriptImage : ScriptMarshalByRefObject, ISerializable
    {
        byte[] _imageFileBytes;
        string _hash; // hash for the original byte data
        System.Drawing.Size _size;
        //System.Drawing.Imaging.ImageFormat _imageFormat;


        [ScriptObjectConstructor("Image", "Initializes a new instance of the Image class of the specified size.")]
        [ScriptObjectConstructorParameter("Width", "A number.")]
        [ScriptObjectConstructorParameter("Height", "A number.")]
        public ScriptImage(ScriptNumber width, ScriptNumber height)
        {
            using (System.Drawing.Bitmap img = new System.Drawing.Bitmap((int)width, (int)height))
            {
                using (MemoryStream ms = new MemoryStream())
                {
                    img.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
                    _imageFileBytes = ms.ToArray();
                    _size = img.Size;
                    //_imageFormat = System.Drawing.Imaging.ImageFormat.Png;
                }
            }
        }

        [ScriptObjectConstructor("Image", "Initializes a new instance of the Image class from the specified file.")]
        [ScriptObjectConstructorParameter("FilePath", "The path to the image file.")]
        public ScriptImage(ScriptString filePath)
        {
            using (System.Drawing.Bitmap img = new System.Drawing.Bitmap((string)filePath))
            {
                using (MemoryStream ms = new MemoryStream())
                {
                    img.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
                    _imageFileBytes = ms.ToArray();
                    _size = img.Size;
                    //_imageFormat = System.Drawing.Imaging.ImageFormat.Png;
                }
            }
        }

        // for internal use
        public ScriptImage(byte[] imageFileBytes)
        {
            if (imageFileBytes == null)
                throw new ArgumentNullException("imageFileBytes");

            // Now generate a hash code for this file using MD5.
            System.Security.Cryptography.HashAlgorithm alg = System.Security.Cryptography.HashAlgorithm.Create("MD5");
            byte[] fileHashValue = alg.ComputeHash(imageFileBytes);
            _hash = BitConverter.ToString(fileHashValue);

            using (MemoryStream ms = new MemoryStream(imageFileBytes))
            {
                using (System.Drawing.Image img = System.Drawing.Image.FromStream(ms))
                {
                    _size = img.Size;
                    //_imageFormat = img.RawFormat;
                }
            }


            _imageFileBytes = imageFileBytes;
        }

        // for internal use
        public ScriptImage(System.Drawing.Image img)
        {
            if (img == null)
                throw new ArgumentNullException("img");

            using (MemoryStream ms = new MemoryStream())
            {
                img.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
                _imageFileBytes = ms.ToArray();
            }

            // Now generate a hash code for this file using MD5.
            System.Security.Cryptography.HashAlgorithm alg = System.Security.Cryptography.HashAlgorithm.Create("MD5");
            byte[] fileHashValue = alg.ComputeHash(_imageFileBytes);
            _hash = BitConverter.ToString(fileHashValue);

            _size = img.Size;
            //_imageFormat = img.RawFormat;
        }
        public byte[] PrimitiveBytes
        {
            get
            {
                return _imageFileBytes;
            }
        }

        public string PrimitiveDataHash
        {
            get
            {
                // Since the image data can be a lot to transfer in remoting, se the image hash to determine if the image needs retreiving (like if it has changed).
                return _hash;
            }
        }

        public override string ToString()
        {
            return "{" + _size.Width + " x " + _size.Height + ", " + _imageFileBytes.Length + " bytes}";
        }

        [ScriptObjectPropertyAttribute("Width", "Gets the image width.")]
        public ScriptNumber Width
        {
            get { return new ScriptNumber(_size.Width); }
        }

        [ScriptObjectPropertyAttribute("Height", "Gets the image height.")]
        public ScriptNumber Height
        {
            get { return new ScriptNumber(_size.Height); }
        }


        [ScriptObjectPropertyAttribute("Length", "Gets the length of the byte array representation of the image..")]
        public ScriptNumber Length
        {
            get { return new ScriptNumber(_imageFileBytes.Length); }
        }

        [ScriptObjectPropertyAttribute("Hash", "Gets the MD5 hash of the byte array.")]
        public ScriptString Hash
        {
            get { return new ScriptString(_hash); }
        }

        public override object ToPrimitiveObject()
        {
            return (object)_imageFileBytes;
        }

        //public static explicit operator byte[](ScriptImage value)
        //{
        //    return value.PrimitiveBytes;
        //}

        public static implicit operator byte[](ScriptImage value)
        {
            return value.PrimitiveBytes;
        }
        
        #region ISerializable Members
        protected ScriptImage(SerializationInfo info, StreamingContext context)
        {
            if (info == null) throw new System.ArgumentNullException("info");
            _imageFileBytes = (byte[])info.GetValue("d", typeof(byte[]));
            _hash = info.GetString("h");
            _size = (System.Drawing.Size)info.GetValue("s", typeof(System.Drawing.Size));
        }

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("d", _imageFileBytes, typeof(byte[]));
            info.AddValue("h", _hash);
            info.AddValue("s", _size);
        }
        #endregion
    }


    [Serializable]
    [ScriptObjectClass("Dictionary", "Represents a collection of key/value pairs that are ordered based on the key/index. The key is a case-insensitive string.")]
    public class ScriptDictionary : ScriptMarshalByRefObject, ISerializable // key is case-insensitive
    {
        private OrderedDictionary _dictionary;

        [ScriptObjectConstructor("Dictionary", "Initializes a new empty Dictionary instance.")]
        public ScriptDictionary()
        {
            // Use CaseInsensitiveEqualityComparer to make the string key case-insensitive
            _dictionary = new OrderedDictionary(new CaseInsensitiveEqualityComparer());
        }

        public IScriptObject Get(int index) // for use within C# code, not from within the script
        {
            return Get(new ScriptNumber(index));
        }

        public IScriptObject Get(string key) // for use within C# code, not from within the script
        {
            return Get(new ScriptString(key));
        }

        [ScriptObjectMethodAttribute("Get Value by Index", "Get the entry from the dictionary at the specified index.")]
        [ScriptObjectMethodParameter("index", "The zero-based index of the value to retrieve.")]
        public IScriptObject Get(ScriptNumber index)
        {
            object dictitem = _dictionary[(int)index];
            return (IScriptObject)dictitem;
        }
        [ScriptObjectMethodAttribute("Get Value by Key", "Get the entry from the dictionary with the specified key.")]
        [ScriptObjectMethodParameter("key", "The key of the value to retrieve.")]
        public IScriptObject Get(ScriptString key)
        {
            object dictitem = _dictionary[(string)key];

            if (dictitem == null)
                return null;
            else
                return (IScriptObject)dictitem;
        }

        public ScriptString GetKey(int index)
        {
            return GetKey(new ScriptNumber(index));
        }

        [ScriptObjectMethodAttribute("Get Key by Index", "Get the key from the dictionary at the specified index.")]
        [ScriptObjectMethodParameter("index", "The zero-based index of the key to retrieve.")]
        public ScriptString GetKey(ScriptNumber index)
        {
            string[] arr = new string[_dictionary.Count];
            _dictionary.Keys.CopyTo(arr, 0);
            return new ScriptString(arr[(int)index]);
        }

        [ScriptObjectMethodAttribute("Add value", "Adds an entry with the specified value into the dictionary with the lowest available index.")]
        [ScriptObjectMethodParameter("value", "The value of the entry to add.")]
        public void Add(ScriptString value)
        {
            _dictionary.Add(((string)value).GetHashCode(), value);
        }
        [ScriptObjectMethodAttribute("Add with key", "Adds an entry with the specified key and value into the dictionary with the lowest available index.")]
        [ScriptObjectMethodParameter("key", "The key of the entry to add.")]
        [ScriptObjectMethodParameter("value", "The value of the entry to add.")]
        public void Add(ScriptString key, IScriptObject value)
        {
            // dictionary = Dict.Add( string key, object value )
            _dictionary.Add((string)key, value);
        }
        [ScriptObjectMethodAttribute("Insert", "Inserts a new entry into the dictionary with the specified key and value at the specified index.")]
        [ScriptObjectMethodParameter("index", "The zero-based index at which the element should be inserted.")]
        [ScriptObjectMethodParameter("key", "The key of the entry to add.")]
        [ScriptObjectMethodParameter("value", "The value of the entry to add.")]
        public void Insert(ScriptNumber index, ScriptString key, IScriptObject value)
        {
            // dictionary = Dict.Insert( numeric index, string key, value )
            _dictionary.Insert((int)index, (string)key, value);
        }
        [ScriptObjectMethodAttribute("Contains", "Determines whether the dictionary contains the specified key.")]
        [ScriptObjectMethodParameter("key", "The key to locate in the dictionary.")]
        public ScriptBoolean Contains(ScriptString key)
        {
            // bool = Dict.Contains( string key )
            return new ScriptBoolean(_dictionary.Contains((string)key));
        }
        [ScriptObjectMethodAttribute("Remove by Key", "Removes the element at the specified key.")]
        [ScriptObjectMethodParameter("key", "The key of the entry to remove.")]
        public void Remove(ScriptString key)
        {
            // dictionary = Dict.Remove( string key )
            _dictionary.Remove((string)key);
        }
        [ScriptObjectMethodAttribute("Remove by Index", "Removes the element at the specified index.")]
        [ScriptObjectMethodParameter("index", "The zero-based index of the entry to remove.")]
        public void RemoveAt(ScriptNumber index)
        {
            // dictionary = Dict.RemoveAt( number index )
            _dictionary.RemoveAt((int)index);
        }
        //[ScriptObjectMethodAttribute]
        //public IScriptObject Move(ScriptNumber fromindex, ScriptNumber toindex)
        //{
        //    // dictionary = Dict.Move( from index, to index )
        //    _dictionary..Move((int)fromindex, (int)toindex);
        //    return null, TokenTypeEnum.Empty);
        //}
        [ScriptObjectMethodAttribute("Clear", "Removes all elements from the dictionary.")]
        public void Clear()
        {
            // dictionary = Dict.Clear()
            _dictionary.Clear();
        }

        [ScriptObjectPropertyAttribute("Count", "Gets the number of items in the dictionary.")]
        public ScriptNumber Count
        {
            get { return new ScriptNumber(_dictionary.Count); }
        }

        public int CountInternal
        {
            get { return _dictionary.Count; }
        }

        public override string ToString()  // so we can see the value in the debugger
        {
            return "Count = " + Count;
        }

        #region ISerializable Members
        protected ScriptDictionary(SerializationInfo info, StreamingContext context)
        {
            if (info == null) throw new System.ArgumentNullException("info");
            _dictionary = (OrderedDictionary)info.GetValue("d", typeof(OrderedDictionary));
        }

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("d", _dictionary, typeof(OrderedDictionary));
        }
        #endregion
    }

    [ScriptObjectClass("FileSystem", "Provides static methods for common file system operations. This object can not be instantiated in a script.")]
    public class ScriptFileSystem : ScriptMarshalByRefObject
    {
        private ScriptFileSystem()  // Prevent the user from creating an instance of this type.
        {
        }
        
        [ScriptObjectMethodAttribute("Copy File", "Copies an existing file to a new one.")]
        [ScriptObjectMethodParameter("sourceFilePath", "The path of the file to copy.")]
        [ScriptObjectMethodParameter("destFilePath", "The destination file path.")]
        public static void CopyFile(ScriptString sourceFilePath, ScriptString destFilePath)
        {
            CopyFile(sourceFilePath, destFilePath, new ScriptBoolean(false));
        }

        [ScriptObjectMethodAttribute("Copy File (overwritable)", "Copies an existing file to a new one. Overwriting a file of the same name is allowed.")]
        [ScriptObjectMethodParameter("sourceFilePath", "The path of the file to copy.")]
        [ScriptObjectMethodParameter("destFilePath", "The destination file path.")]
        [ScriptObjectMethodParameter("overwrite", "true if the destination file can be overwritten, otherwise false.")]
        public static void CopyFile(ScriptString sourceFilePath, ScriptString destFilePath, ScriptBoolean overwrite)
        {
            File.Copy((string)sourceFilePath, (string)destFilePath, (bool)overwrite);
        }

        [ScriptObjectMethodAttribute("Move File", "Moves an existing file to a new location, providing the option to specify a new filename.")]
        [ScriptObjectMethodParameter("sourceFilePath", "The path of the file to move.")]
        [ScriptObjectMethodParameter("destFilePath", "The path of the file to move.")]
        public static void MoveFile(ScriptString sourceFilePath, ScriptString destFilePath)
        {
            File.Move((string)sourceFilePath, (string)destFilePath);
        }

        [ScriptObjectMethodAttribute("File Exists", "Determines whether the specified file exists.")]
        [ScriptObjectMethodParameter("filePath", "The path of the directory to check.")]
        public static ScriptBoolean FileExists(ScriptString filePath)
        {
            return new ScriptBoolean(File.Exists((string)filePath));
        }

        [ScriptObjectMethodAttribute("Delete File", "Deletes the specified file. An error will not occur if the specified file does not exist.")]
        [ScriptObjectMethodParameter("path", "The path of the file to be deleted.")]
        public static void DeleteFile(ScriptString path)
        {
            File.Delete((string)path);
        }

        [ScriptObjectMethodAttribute("Create Directory", "Creates all the directories in the specified path.")]
        [ScriptObjectMethodParameter("directoryPath", "The directory path to create.")]
        public static void CreateDirectory(ScriptString directoryPath)
        {
            Directory.CreateDirectory((string)directoryPath);
        }

        [ScriptObjectMethodAttribute("Move Directory", "Moves a file or directory and it's contents to a new location.")]
        [ScriptObjectMethodParameter("sourceDirPath", "The path of the file or directory to move.")]
        [ScriptObjectMethodParameter("destDirPath", "The path of the new location for sourceDirPath.")]
        public static void MoveDirectory(ScriptString sourceDirPath, ScriptString destDirPath)
        {
            Directory.Move((string)sourceDirPath, (string)destDirPath);
        }

        [ScriptObjectMethodAttribute("Directory Exists", "Determines whether the specified directory exists.")]
        [ScriptObjectMethodParameter("directoryPath", "The path of the directory to check.")]
        public static ScriptBoolean DirectoryExists(ScriptString directoryPath)
        {
            return new ScriptBoolean(Directory.Exists((string)directoryPath));
        }

        [ScriptObjectMethodAttribute("Delete Directory", "Deletes the specified directory.")]
        [ScriptObjectMethodParameter("directoryPath", "The path of the empty directory to delete.")]
        public static void DeleteDirectory(ScriptString directoryPath)
        {
            Directory.Delete((string)directoryPath);
        }

        [ScriptObjectMethodAttribute("Delete Directory (recursive)", "Deletes the specified directory, and if indicated any subdirectories and files in the path.")]
        [ScriptObjectMethodParameter("directoryPath", "The path of the empty directory to delete. The directory must be writable and empty.")]
        [ScriptObjectMethodParameter("recursive", "true to delete directories, subdirectories, and files in the specified path, false otherwise.")]
        public static void DeleteDirectory(ScriptString directoryPath, ScriptBoolean recursive)
        {
            Directory.Delete((string)directoryPath, (bool)recursive);
        }
    }


    [ScriptObjectClass("OleDbConnection", "Represents a connection to a data source (such as a database). With a client/server database system, it is equivalent to a network connection to the server.")]
    public class ScriptOleDbConnection : ScriptMarshalByRefObject
    {
        // OLEDB is the successor to ODBC
        OleDbConnection _connection;

        [ScriptObjectConstructor("OleDbConnection", "Initializes a new OleDbConnection instance with the specified OLE DB connection string.")]
        [ScriptObjectConstructorParameter("connectionString", "The connection used to open the data source.")]
        public ScriptOleDbConnection(ScriptString connectionString)
        {
            _connection = new OleDbConnection((string)connectionString);
            _connection.Open();
        }

        public OleDbConnection Connection
        {
            get { return _connection; }
        }

        [ScriptObjectMethodAttribute("Close", "Closes the connection to the data source.")]
        public void Close()
        {
            _connection.Close();
        }

        [ScriptObjectMethodAttribute("Execute", "Executes a SQL command (such as SQL INSERT, DELETE, UPDATE, and SET statements) against the connection and returns the number of rows affected.")]
        [ScriptObjectMethodParameter("sqlStatement", "The text of the sql statement.")]
        public ScriptNumber Execute(ScriptString sqlStatement)
        {
            OleDbCommand command = new OleDbCommand((string)sqlStatement, _connection);
            return new ScriptNumber(command.ExecuteNonQuery());
        }

        [ScriptObjectMethodAttribute("Get Value", "Executes the query and returns the first column of the first row in the result set by the query. Additional columns and rows are ignored.")]
        [ScriptObjectMethodParameter("sqlStatement", "The text of the sql statement.")]
        public IScriptObject GetValue(ScriptString sqlStatement)
        {
            return GetValue(sqlStatement, null);
        }

        [ScriptObjectMethodAttribute("Get Value", "Executes the query and returns the first column of the first row in the result set by the query if any, otherwise returns the default value. Additional columns and rows are ignored.")]
        [ScriptObjectMethodParameter("sqlStatement", "The text of the sql statement.")]
        [ScriptObjectMethodParameter("defaultValue", "The value to return if the result set is empty.")]
        public IScriptObject GetValue(ScriptString sqlStatement, IScriptObject defaultValue)
        {
            OleDbCommand command = new OleDbCommand((string)sqlStatement, _connection);
            object o = command.ExecuteScalar();

            if (o is DBNull)
                return defaultValue;
            if (o is string)
                return new ScriptString((string)o);
            else if (o is bool)
                return new ScriptBoolean((bool)o);
            else if (o is DateTime)
                return new ScriptDateTime((DateTime)o);
            else if (IsNumericDataType(o.GetType()))
                return new ScriptNumber(Convert.ToDouble(o));
            else
                return new ScriptString((string)o);
        }

        [ScriptObjectMethodAttribute("Get Data Table", "Executes the SQL SELECT query and returns a DataTable instance containing the result set.")]
        [ScriptObjectMethodParameter("sqlStatement", "A string that is an SQL SELECT statement or stored procedure.")]
        public ScriptDataTable GetDataTable(ScriptString sqlStatement)
        {
            DataTable dt = new DataTable();

            OleDbDataAdapter da = new OleDbDataAdapter((string)sqlStatement, _connection);
            da.Fill(dt);

            return new ScriptDataTable(dt);
        }
    }

    [ScriptObjectClass("OdbcConnection", "Represents a connection to a data source (such as a database). With a client/server database system, it is equivalent to a network connection to the server.")]
    public class ScriptOdbcConnection : ScriptMarshalByRefObject
    {
        // I added support for ODBC since there is no MySql OleDb provider directly from MySql.
        // Example MySql ODBC connection string: "Driver={MySQL ODBC 5.1 Driver};Server=localhost;Database=mydbname; User=root;Password=password;Option=3;"

        OdbcConnection _connection;

        [ScriptObjectConstructor("OdbcConnection", "Initializes a new OdbcConnection instance with the specified connection string.")]
        [ScriptObjectConstructorParameter("connectionString", "The connection used to open the data source.")]
        public ScriptOdbcConnection(ScriptString connectionString)
        {
            _connection = new OdbcConnection((string)connectionString);
            _connection.Open();
        }

        public OdbcConnection Connection
        {
            get { return _connection; }
        }

        [ScriptObjectMethodAttribute("Close", "Closes the connection to the data source.")]
        public void Close()
        {
            _connection.Close();
        }

        [ScriptObjectMethodAttribute("Execute", "Executes a SQL command (such as SQL INSERT, DELETE, UPDATE, and SET statements) against the connection and returns the number of rows affected.")]
        [ScriptObjectMethodParameter("sqlStatement", "The text of the sql statement.")]
        public ScriptNumber Execute(ScriptString sqlStatement)
        {
            OdbcCommand command = new OdbcCommand((string)sqlStatement, _connection);
            return new ScriptNumber(command.ExecuteNonQuery());
        }

        [ScriptObjectMethodAttribute("Get Value", "Executes the query and returns the first column of the first row in the result set by the query. Additional columns and rows are ignored.")]
        [ScriptObjectMethodParameter("sqlStatement", "The text of the sql statement.")]
        public IScriptObject GetValue(ScriptString sqlStatement)
        {
            return GetValue(sqlStatement, null);
        }

        [ScriptObjectMethodAttribute("Get Value", "Executes the query and returns the first column of the first row in the result set by the query if any, otherwise returns the default value. Additional columns and rows are ignored.")]
        [ScriptObjectMethodParameter("sqlStatement", "The text of the sql statement.")]
        [ScriptObjectMethodParameter("defaultValue", "The value to return if the result set is empty.")]
        public IScriptObject GetValue(ScriptString sqlStatement, IScriptObject defaultValue)
        {
            OdbcCommand command = new OdbcCommand((string)sqlStatement, _connection);
            object o = command.ExecuteScalar();

            if (o is DBNull)
                return defaultValue;
            if (o is string)
                return new ScriptString((string)o);
            else if (o is bool)
                return new ScriptBoolean((bool)o);
            else if (o is DateTime)
                return new ScriptDateTime((DateTime)o);
            else if (IsNumericDataType(o.GetType()))
                return new ScriptNumber(Convert.ToDouble(o));
            else
                return new ScriptString((string)o);
        }

        [ScriptObjectMethodAttribute("Get Data Table", "Executes the SQL SELECT query and returns a DataTable instance containing the result set.")]
        [ScriptObjectMethodParameter("sqlStatement", "A string that is an SQL SELECT statement or stored procedure.")]
        public ScriptDataTable GetDataTable(ScriptString sqlStatement)
        {
            DataTable dt = new DataTable();

            OdbcDataAdapter da = new OdbcDataAdapter((string)sqlStatement, _connection);
            da.Fill(dt);

            return new ScriptDataTable(dt);
        }
    }

    [Serializable]
    [ScriptObjectClass("DataTable", "Represents one table of in-memory data retrieved from a database using a OleDbConnection or OdbcConnection instance.")]
    public class ScriptDataTable : ScriptMarshalByRefObject, ISerializable
    {
        System.Data.DataTable _dataTable;

        public ScriptDataTable(DataTable dataTable)
        {
            _dataTable = dataTable;
        }

        [ScriptObjectMethodAttribute("Get Required Field String", "Retrieves a value from the DataTable at the specified row and column as a String.")]
        [ScriptObjectMethodParameter("rowNumber", "The row number")]
        [ScriptObjectMethodParameter("columnName", "The name of the column.")]
        public ScriptString GetFieldString(ScriptNumber rowNum, ScriptString colName)
        {
            // fieldvalue = DataTable.GetFieldString(rownum, colname)  //rownum and colnum are zero based.
            return new ScriptString(Convert.ToString(_dataTable.Rows[(int)rowNum][(string)colName]));
        }
        [ScriptObjectMethodAttribute("Get Field String", "Retrieves a value from the DataTable at the specified row and column as a String. If the value is null, then the specified default value is returned.")]
        [ScriptObjectMethodParameter("rowNumber", "The row number")]
        [ScriptObjectMethodParameter("columnName", "The name of the column.")]
        [ScriptObjectMethodParameter("defaultValue", "The value to return if the specified row/column value is null.")]
        public ScriptString GetFieldString(ScriptNumber rowNum, ScriptString colName, ScriptString defaultValue)
        {
            // fieldvalue = DataTable.GetFieldString(rownum, colname, defaultstringvalue)  //rownum and colnum are zero based.

            object o = _dataTable.Rows[(int)rowNum][(string)colName];
            if (o is DBNull)
                return defaultValue;
            else
                return new ScriptString(Convert.ToString(o));
        }

        [ScriptObjectMethodAttribute("Get Required Field Number", "Retrieves a value from the DataTable at the specified row and column as a Number.")]
        [ScriptObjectMethodParameter("rowNumber", "The row number")]
        [ScriptObjectMethodParameter("columnName", "The name of the column.")]
        public ScriptNumber GetFieldNumber(ScriptNumber rowNum, ScriptString colName)
        {
            // fieldvalue = DataTable.GetFieldNumber(rownum, colname)  //rownum and colnum are zero based.
            return new ScriptNumber(Convert.ToDouble(_dataTable.Rows[(int)rowNum][(string)colName]));
        }
        [ScriptObjectMethodAttribute("Get Field Number", "Retrieves a value from the DataTable at the specified row and column as a Number. If the value is null, then the specified default value is returned.")]
        [ScriptObjectMethodParameter("rowNumber", "The row number")]
        [ScriptObjectMethodParameter("columnName", "The name of the column.")]
        [ScriptObjectMethodParameter("defaultValue", "The value to return if the specified row/column value is null.")]
        public ScriptNumber GetFieldNumber(ScriptNumber rowNum, ScriptString colName, ScriptNumber defaultValue)
        {
            // fieldvalue = DataTable.GetFieldNumber(rownum, colname, defaultnumbervalue)  //rownum and colnum are zero based.

            object o = _dataTable.Rows[(int)rowNum][(string)colName];
            if (o is DBNull)
                return defaultValue;
            else
                return new ScriptNumber(Convert.ToDouble(o));
        }

        [ScriptObjectMethodAttribute("Row Count", "Gets the number of rows in the DataTable.")]
        public ScriptNumber RowCount
        {
            get { return new ScriptNumber((double)_dataTable.Rows.Count); }
        }

        public override string ToString()  // so we can see the value in the debugger
        {
            return "Row Count = " + _dataTable.Rows.Count;
        }

        #region ISerializable Members
        protected ScriptDataTable(SerializationInfo info, StreamingContext context)
        {
            if (info == null) throw new System.ArgumentNullException("info");
            _dataTable = (DataTable)info.GetValue("d", typeof(DataTable));
        }

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("d", _dataTable, typeof(DataTable));
        }
        #endregion
    }

    [Serializable]
    [ScriptObjectClass("Text File Writer", "Writes data to a file.")]
    public class ScriptTextFileWriter : ScriptMarshalByRefObject, ISerializable
    {
        StreamWriter _streamWriter;

        // for internal use
        public ScriptTextFileWriter(StreamWriter sw)
        {
            _streamWriter = sw;
        }

        [ScriptObjectMethodAttribute("Write All Text", "Creates a new file, writes the specified string to the file. If the target file already exists, it is overwritten.")]
        [ScriptObjectMethodParameter("Path", "The file to write the specified string to.")]
        [ScriptObjectMethodParameter("Contents", "The string to write to the file.")]
        public static void WriteAllText(ScriptString path, ScriptString contents)
        {
            File.WriteAllText((string)path, (string)contents);
        }

        [ScriptObjectMethodAttribute("Append All Text", "Appends the specified string to the file, creating the file if it does not already exist.")]
        [ScriptObjectMethodParameter("Path", "The file to append the specified string to.")]
        [ScriptObjectMethodParameter("Contents", "The string to append to the file.")]
        public static void AppendFile(ScriptString path, ScriptString contents)
        {
            File.AppendAllText((string)path, (string)contents);
        }

        [ScriptObjectMethodAttribute("Create Text", "Creates or opens a file for writing UTF-8 encoded text.")]
        [ScriptObjectMethodParameter("Path", "The file to be opened for writing.")]
        public static ScriptTextFileWriter CreateText(ScriptString path)
        {
            StreamWriter sw = File.CreateText((string)path);
            return new ScriptTextFileWriter(sw);
        }

        [ScriptObjectMethodAttribute("Write", "Writes the text representation of a boolean to the file.")]
        [ScriptObjectMethodParameter("value", "The value to write to the file.")]
        public void Write(ScriptBoolean value)
        {
            _streamWriter.Write((bool)value);
        }

        [ScriptObjectMethodAttribute("Write", "Writes a string to the file.")]
        [ScriptObjectMethodParameter("value", "The value to write to the file.")]
        public void Write(ScriptString value)
        {
            _streamWriter.Write((string)value);
        }

        [ScriptObjectMethodAttribute("Write", "Writes a number to the file.")]
        [ScriptObjectMethodParameter("value", "The value to write to the file.")]
        public void Write(ScriptNumber value)
        {
            _streamWriter.Write((double)value);
        }





        [ScriptObjectMethodAttribute("Write Line", "Writes the text representation of a boolean followed by a line terminator to the file.")]
        [ScriptObjectMethodParameter("value", "The value to write to the file.")]
        public void WriteLine(ScriptBoolean value)
        {
            _streamWriter.WriteLine((bool)value);
        }

        [ScriptObjectMethodAttribute("Write Line", "Writes a string followed by a line terminator to the file.")]
        [ScriptObjectMethodParameter("value", "The value to write to the file.")]
        public void WriteLine(ScriptString value)
        {
            _streamWriter.WriteLine((string)value);
        }

        [ScriptObjectMethodAttribute("Write Line", "Writes a number followed by a line terminator to the file.")]
        [ScriptObjectMethodParameter("value", "The value to write to the file.")]
        public void WriteLine(ScriptNumber value)
        {
            _streamWriter.WriteLine((double)value);
        }


        [ScriptObjectMethodAttribute("Close", "Closes the current file.")]
        public void Close()
        {
            if (_streamWriter != null)
            {
                _streamWriter.Close();
                _streamWriter = null;
            }
        }

        #region ISerializable Members
        protected ScriptTextFileWriter(SerializationInfo info, StreamingContext context)
        {
            if (info == null) throw new System.ArgumentNullException("info");
            _streamWriter = (StreamWriter)info.GetValue("s", typeof(StreamWriter));
        }

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("s", _streamWriter, typeof(StreamWriter));
        }
        #endregion
    }

    [Serializable]
    [ScriptObjectClass("Text File Reader", "Reads data from a file.")]
    public class ScriptTextFileReader : ScriptMarshalByRefObject, ISerializable
    {
        StreamReader _streamReader;

        // for internal use
        public ScriptTextFileReader(StreamReader sr)
        {
            _streamReader = sr;
        }

        [ScriptObjectMethodAttribute("Read All Text", "Opens a file, reads all the text from the file, then closes it.")]
        [ScriptObjectMethodParameter("Path", "The file to read.")]
        public static ScriptString ReadAllText(ScriptString path)
        {
            string s = File.ReadAllText((string)path);
            return new ScriptString(s);
        }

        [ScriptObjectMethodAttribute("Open Text", "Opens an existing UTF-8 file for reading.")]
        [ScriptObjectMethodParameter("Path", "The file to be opened for reading.")]
        public static ScriptTextFileReader OpenText(ScriptString path)
        {
            StreamReader sr = File.OpenText((string)path);
            return new ScriptTextFileReader(sr);
        }

        [ScriptObjectMethodAttribute("Read Line", "Reads a line of characters from the current file and returns the data as a string.")]
        public ScriptString ReadLine()
        {
            string s = _streamReader.ReadLine();
            if (s != null)
                return new ScriptString(s);
            else
                return new ScriptString();
        }

        [ScriptObjectMethodAttribute("Read To End", "Reads the file from the current position to the end of the file.")]
        public ScriptString ReadToEnd()
        {
            string s = _streamReader.ReadToEnd();
            return new ScriptString(s);
        }

        [ScriptObjectMethodAttribute("Peek", "Reads the the next available character but does not consume it, or returns an empty string if no more characters are available.")]
        public ScriptString Peek()
        {
            int i = _streamReader.Peek();
            if (i != -1)
            {
                char c = (char)i;
                string s = c.ToString();
                return new ScriptString(s);
            }
            else
                return new ScriptString();
        }

        [ScriptObjectProperty("End Of File", "Gets a value that indicates whether the current position is at the end of the file.")]
        public ScriptBoolean EndOfFile
        {
            get
            {
                return new ScriptBoolean(_streamReader.EndOfStream);
            }
        }

        [ScriptObjectMethodAttribute("Close", "Closes the current file.")]
        public void Close()
        {
            if (_streamReader != null)
            {
                _streamReader.Close();
                _streamReader = null;
            }
        }

        #region ISerializable Members
        protected ScriptTextFileReader(SerializationInfo info, StreamingContext context)
        {
            if (info == null) throw new System.ArgumentNullException("info");
            _streamReader = (StreamReader)info.GetValue("s", typeof(StreamReader));
        }

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("s", _streamReader, typeof(StreamReader));
        }
        #endregion
    }


    [Serializable]
    [ScriptObjectClass("RegEx", "Represents an immutable regular expression.")]
    public class ScriptRegEx : ScriptObject, ISerializable
    {
        System.Text.RegularExpressions.Regex _regEx;

        [ScriptObjectConstructor("RegEx", "Initializes and compiles a new instance of the RegEx class for the specified regular expression.")]
        [ScriptObjectConstructorParameter("pattern", "The regular expression pattern to match.")]
        public ScriptRegEx(ScriptString pattern) : this (pattern, new ScriptNumber(0))
        {
        }

        [ScriptObjectConstructor("RegEx", "Initializes and compiles a new instance of the Regex class for the specified regular expression, with options that modify the pattern.")]
        [ScriptObjectConstructorParameter("pattern", "The regular expression pattern to match.")]
        [ScriptObjectConstructorParameter("options", "A bitwise OR combination of Regex Option values. 0=None, 1=Case-insensitive, 2=Multi-line, 4=Explicit Capture, 8=Compiled, 16=Single Line, 32=Ignore Pattern Whitespace, 64=Right To Left, 256=ECMA Script, 512=Culture Invariant")]
        public ScriptRegEx(ScriptString pattern, ScriptNumber options)
        {
            _regEx = new System.Text.RegularExpressions.Regex((string)pattern, (System.Text.RegularExpressions.RegexOptions)(int)options);
        }

        [ScriptObjectMethodAttribute("Is Match", "Indicates whether the regular expression finds a match in the input string using the regular expression specified in the pattern parameter.")]
        [ScriptObjectMethodParameter("input", "The string to search for a match.")]
        public ScriptBoolean IsMatch(ScriptString input)
        {
            return new ScriptBoolean(_regEx.IsMatch((string)input));
        }

        [ScriptObjectMethodAttribute("Is Match", "Indicates whether the regular expression specified in the Regex constructor finds a match in the input string beginning at the specified starting position in the string.")]
        [ScriptObjectMethodParameter("input", "The string to search for a match.")]
        [ScriptObjectMethodParameter("startAt", "The character position at which to start the search.")]
        public ScriptBoolean IsMatch(ScriptString input, ScriptNumber startAt)
        {
            return new ScriptBoolean(_regEx.IsMatch((string)input, (int)startAt));
        }

        [ScriptObjectMethodAttribute("Is Match", "Indicates whether the regular expression finds a match in the input string using the regular expression specified in the pattern parameter.")]
        [ScriptObjectMethodParameter("input", "The string to search for a match.")]
        [ScriptObjectMethodParameter("pattern", "The regular expression pattern to match.")]
        public static ScriptBoolean IsMatch(ScriptString input, ScriptString pattern)
        {
            return new ScriptBoolean(System.Text.RegularExpressions.Regex.IsMatch((string)input, (string)pattern));
        }

        [ScriptObjectMethodAttribute("Split", "Splits the specified input string a specified maximum number of times at the positions defined by a regular expression specified in the Regex constructor.")]
        [ScriptObjectMethodParameter("input", "The string to split.")]
        public IScriptArray Split(ScriptString input)
        {
            return Split(input, new ScriptNumber(0), _regEx.RightToLeft ? new ScriptNumber(input.Length) : new ScriptNumber(0));
        }

        [ScriptObjectMethodAttribute("Split", "Splits the specified input string a specified maximum number of times at the positions defined by a regular expression specified in the Regex constructor.")]
        [ScriptObjectMethodParameter("input", "The string to split.")]
        [ScriptObjectMethodParameter("count", "The maximum number of times the split can occur.")]
        public IScriptArray Split(ScriptString input, ScriptNumber count)
        {
            return Split(input, count, _regEx.RightToLeft ? new ScriptNumber(input.Length) : new ScriptNumber(0));
        }

        [ScriptObjectMethodAttribute("Split", "Splits the specified input string a specified maximum number of times at the positions defined by a regular expression specified in the RegEx constructor. The search for the regular expression pattern starts at a specified character position in the input string.")]
        [ScriptObjectMethodParameter("input", "The string to split.")]
        [ScriptObjectMethodParameter("count", "The maximum number of times the split can occur.")]
        [ScriptObjectMethodParameter("startAt", "The character position in the input string where the search will begin.")]
        public IScriptArray Split(ScriptString input, ScriptNumber count, ScriptNumber startAt)
        {
            string[] parts = _regEx.Split((string)input, (int)count, (int)startAt);

            ScriptArrayMarshalByValue array = new ScriptArrayMarshalByValue(true, true);
            for (int i = 0; i < array.Length; i++)
                array.SetItemInternal(i, new ScriptString(parts[i]));

            return array;
        }

        [ScriptObjectMethodAttribute("Split", "Splits the input string at the positions defined by a regular expression pattern.")]
        [ScriptObjectMethodParameter("input", "The string to split.")]
        [ScriptObjectMethodParameter("pattern", "The regular expression pattern to match.")]
        public static IScriptArray Split(ScriptString input, ScriptString pattern)
        {
            string[] parts = System.Text.RegularExpressions.Regex.Split((string)input, (string)pattern);

            ScriptArrayMarshalByValue array = new ScriptArrayMarshalByValue(true, true);
            for (int i=0;i<array.Length; i++)
                array.SetItemInternal(i, new ScriptString(parts[i]));
            
            return array;
        }

        [ScriptObjectProperty("Options", "Returns the options passed into the Regex constructor. 0=None, 1=Case-insensitive, 2=Multi-line, 4=Explicit Capture, 8=Compiled, 16=Single Line, 32=Ignore Pattern Whitespace, 64=Right To Left, 256=ECMA Script, 512=Culture Invariant")]
        public ScriptNumber Options
        {
            get { return new ScriptNumber((int)_regEx.Options); }
        }

        [ScriptObjectProperty("Is Right To Left", "Gets a value indicating whether the regular expression searches from right to left.")]
        public ScriptBoolean IsRightToLeft
        {
            get { return new ScriptBoolean(_regEx.RightToLeft); }
        }

        [ScriptObjectProperty("Is Case-Insensitive", "Gets a value indicating whether the regular expression searches from right to left.")]
        public ScriptBoolean IsCaseInsensitive
        {
            get
            {
                bool b = ((_regEx.Options & System.Text.RegularExpressions.RegexOptions.IgnoreCase) != System.Text.RegularExpressions.RegexOptions.None);

                return new ScriptBoolean(b);
            }
        }

        [ScriptObjectMethodAttribute("Match", "Searches the specified input string for all occurrence of the regular expression specified in the Regex constructor.")]
        [ScriptObjectMethodParameter("input", "The string to search for a match.")]
        public IScriptArray Match(ScriptString input)
        {
            List<ScriptString> items = new List<ScriptString>();
            
            for (System.Text.RegularExpressions.Match m = _regEx.Match((string)input); m.Success; m = m.NextMatch())
                items.Add(new ScriptString(m.Groups[0].ToString()));

            return new ScriptArrayMarshalByValue(items.ToArray(), true, true);
        }

        [ScriptObjectMethodAttribute("Match", "Searches the specified input string for an occurrence of the regular expression supplied in the pattern parameter.")]
        [ScriptObjectMethodParameter("input", "The string to search for a match.")]
        [ScriptObjectMethodParameter("pattern", "The regular expression pattern to match.")]
        public static IScriptArray Match(ScriptString input, ScriptString pattern)
        {
            List<ScriptString> items = new List<ScriptString>();

            for (System.Text.RegularExpressions.Match m = System.Text.RegularExpressions.Regex.Match((string)input, (string)pattern); m.Success; m = m.NextMatch())
                items.Add(new ScriptString(m.Groups[0].ToString()));

            return new ScriptArrayMarshalByValue(items.ToArray(), true, true);
        }

        #region ISerializable Members
        protected ScriptRegEx(SerializationInfo info, StreamingContext context)
        {
            if (info == null) throw new System.ArgumentNullException("info");
            _regEx = (System.Text.RegularExpressions.Regex)info.GetValue("r", typeof(System.Text.RegularExpressions.Regex));
        }

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("r", _regEx, typeof(System.Text.RegularExpressions.Regex));
        }
        #endregion
    }
}
